kubernetesで複数のwebサービスを楽に管理する

複数ドメインの Web サービスを Kubernetes 上で楽に管理するための環境構築を行ったので、手順をメモしておきます。詳細は参考サイトに非常にわかりやすくまとまっているので、ご参照ください。なお、環境は Google Kubernetes Engine を想定しています。

目標

  • 複数の Web サービスとそれに紐付く Kubernetes サービスを、同一の IP で管理する
  • Ingress のバーチャルホスト機能を使う

  • Let’s Encrypt を使い、証明書の自動取得・更新を行う
  • cert-manager を使う

Ingress Controller の選定

Ingress Contoller に、標準であるGLBC(GCE L7 load balancer controller)を使った場合と、 Nginx Ingress Controllerを使った場合では、内部の動きが異なってきます。

GLBC を使った場合は、Ingress をデプロイすると自動的に L7 ロードバランサが生成され、通信を終端します。この L7 ロードバランサを使って、ダイレクトにバックエンドサービスに通信を振り分けます。

一方、Nginx Ingress Controller を使った場合は、L7 ロードバランサは作成されません。代わりに nginx-ingress-controller というサービスが L4 ロードバランサを生成し、通信を終端します。一旦、Nginx Ingress Controller が通信を受けて、Ingress Resource に問い合わせを行い、 改めて L7 レベルのルーティングを行う、という流れです(参考サイトに図が載っています)。この際、ingress の yaml に記載した内容は単なる設定情報(Ingress Resource)としてのみ機能します。

このあたりを理解するのはなかなか骨が折れますが、こちらの記事によくまとまっているので、興味のある方は見てみてください。

今回は Nginx Ingress Controller を選択しました。GLBC には、HTTP から HTTPS へのリダイレクトを行う機能がないためです。

なお、今回の構成では、設定情報はすべて Ingress Resource で管理するため、Nginx Ingress Controller は stateless であり、いつでも削除・再設置できます。

参考サイト

このページでやっていることは、下記の 2 つの記事をミックスしたものです。

手順

1. クラスタ環境の構築

Kubernetes クラスタはすでに構築されているものと仮定します。

2. helm のインストール

helm は kubernetes 用のパッケージマネージャです。これを使うと、かなり楽に Kubernetes 上にパッケージをデプロイできます。

Helm は、クライアントサイドで動くhelmコマンドと、Kubernetes 上で動くTillerというサービス群の二つの要素から構成されています。まず、helm のサイトから CLI バイナリをダウンロードし、パスを通しておきます。その上で、下記を実行します。

# kube-systemネームスペースに、tillerというサービスアカウントを作成
kubectl create serviceaccount tiller --namespace kube-system

# tillerアカウントにcluster-adminの権限をバインドする(与える)
kubectl create clusterrolebinding tiller-binding `
  --clusterrole=cluster-admin `
  --serviceaccount=kube-system:tiller

# Tiller(helmのサーバサイドのサービス群)をデプロイする
helm init --upgrade --service-account tiller

これで、コマンド一発で Kubernetes にサービスをインストールできる環境が整いました。

3. nginx-ingress のインストール

nginx-ingress を helm を使ってインストールします。このコマンドにより、nginx-ingress に必要な各種の Deployment や Service が一括して作成されます。

helm install stable/nginx-ingress \
  --name nginx-ingress \
  --namespace kube-system \
  --set rbac.create=true

コマンドを実行すると、kube-system ネームスペースに、nginx-ingress-controller というサービスがロードバランサーとして作成されます。

すべてのドメインの名前解決先は、このロードバランサに向けるする必要があるため、固定 IP を設定しておきましょう。固定 IP の予約はプロバイダによって方法が異なりますが、GCP の場合は「VPC Network」→「External IP Address」から予約できます。

kubectl edit svc nginx-ingress-controller --namespace=kube-system
# type: LoadBalancer の直下に下記を追加する
loadBalancerIP: '1.23.4.56'

4. DNS の設定変更

使用する予定のドメインを、nginx-ingress-controller サービスのロードバランサの IP に向かうように設定しておきましょう。IP は、kubectl get svc nginx-ingress-controller --namespace=kube-systemで表示されるEXTERNAL-IPです。

5. Ingress の仮設定

とりあえず、TLS を使わない形で Ingress を仮設定します。この段階で Ingress が必要な理由は、cert-manager が Let’s Encrypt から証明書を取得する際に、Ingress Resource に認証用のパスを動的に追加する必要があるためです。

下記の例では、すでにsome-my-serviceというサービス(公開したい Web アプリ)が設置されていると仮定しています。サービスは、type:NodePortで expose されている必要があるので、確認しておいてください。確認は、kubectl get svcで行えます。

バーチャルホストを使用する方式で記述していますので、ドメインを増やすときは、spec.rules の配下にドメインを追加していけば OK です。

# my-ingress.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    # nginx-ingressを、Ingress Contollerとして使用する
    kubernetes.io/ingress.class: nginx
spec:
  rules:
    - host: some.dummy-url.com
      http:
        paths:
          - backend:
              serviceName: some-my-service
              servicePort: 80
kubectl apply -f my-ingress.yaml

6. cert-manager をインストール

下記のコマンドで cert-manager をインストールします。

helm install --name cert-manager --version v0.3.1 `
  --namespace kube-system stable/cert-manager

7. ClusterIssuer の作成

LetsEncrypt を ClusterIssuer (証明書の発行者)として設定します。失敗を重ねるとペナルティを受ける場合があるみたいなので、テスト用と本番用を用意します。

# cluster-issuer.yaml

apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging # テスト用
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: 'your@address.com'
    privateKeySecretRef:
      name: letsencrypt-staging
    http01: {}
---
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod # 本番用
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: 'your@address.com'
    privateKeySecretRef:
      name: letsencrypt-prod
    http01: {}
kubectl apply -f cluster-issuer.yaml

8. Certificate の作成、証明書の自動取得

下記の yaml を apply すると、cert-manager によって自動的に証明書の取得が始まります。10 分くらいかかります。

ここで何が起こっているかの詳細は、こちらのページを参照してください。簡潔に書くと、Ingress Resource に認証用の一時的なルール(パス)を動的に設定し、これを使って Let’s Encrypt に認証をさせているようです。

# certificate.yaml

apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: some-dummyurl-tls # 証明書を格納するSecretにつける名前
  namespace: default
spec:
  secretName: some-dummyurl-tls # 証明書を格納するSecretにつける名前
  issuerRef:
    # ClusterIssuerを指定する。
    name: letsencrypt-staging
    kind: ClusterIssuer
  commonName: some.dummy-url.com # ドメイン
  dnsNames:
    - some.dummy-url.com # ドメイン
  acme:
    config:
      - http01:
          ingress: my-ingress # 使用しているIngressの名前
        domains:
          - some.dummy-url.com # ドメイン
kubectl apply -f certificate.yaml

証明書の取得状況は、kubectl describe -f certificate.yamlで随時確認できます。Certificate issued successfullyなどと表示されれば、証明書の取得は完了しています。結構時間がかかるので辛抱強さが必要です。

証明書の取得が完了すると、今回の場合はsome-dummyurl-tlsというシークレットに証明書が格納されます。

無事に取得ができることを確認できたら、上記のletsencrypt-stagingletsencrypt-prodに変更して、再度証明書を取得します。

9. TLS を使用する

取得した証明書を使うため、Ingress の設定を変更します。ドメインが増えた場合は、spec.tls と spec.rules の配下を追加することで対応します。

nginx-ingress の設定は、Ingress の設定の中でannotationを使って管理できます。下記の例では、http を https へリダイレクトする設定を入れています。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/ssl-redirect: 'true' # http=>httpsへリダイレクトする
spec:
  tls: # 取得した証明書を使って通信させる
    - secretName: some-dummyurl-tls
      hosts:
        - some.dummy-url.com
  rules:
    - host: some.dummy-url.com
      http:
        paths:
          - backend:
              serviceName: some-my-service
              servicePort: 80

もし、nginx-ingress の使用をやめて 標準の Ingress Controller である GLBC に戻したい場合は、下記の手順を行います。

  • Ingress の定義を下記の通り変更し、再デプロイする。
  • # 下記の行を削除する
    kubernetes.io/ingress.class: nginx
    
    # もしくは下記の通り記述する(記載がない場合は暗黙的にgceが指定されますが、明示してもOK)
    kubernetes.io/ingress.class: gce
  • DNS の向き先を GLBC(L7 ロードバランサ)に変更する。IP はkubectl get ingressで取得できる。

なお、Ingress Controller の種類にかかわらず、設定情報は Ingress Resource によって抽象化されているため、cert-manager はどちらの環境でも問題なく動作します。

注意事項

  • Ingress の設定は反映されるまでに 5 ~ 10 分程度かかります。エラーがでても、しばらくたつと問題なく動作していたりします。この点は、現状では我慢するしかありません。

所感

Kubernetes は 2 年ぶりぐらいに触りましたが、かなり進化していて嬉しくなりました。昨今、インフラがどんどん Stateless になっていくのを感じます。インフラの定義はコードになって、コマンド一発で同じ環境を再現できるようになりました。

また、今回の取り組みは、「どうやったら Kubernetes で複数の Web サービスを一括管理できるか?」と思い立ってから、上記の構成を構築するまで、たったの 1 日で完了させることができました。

いい時代です。