SUZURI SRE Kubernetes AWS

SUZURIのマルチクラウド化で乗り越えたクラウド毎の「差分」を紹介します

SUZURI SRE Kubernetes AWS

こんにちは。技術部プラットフォームグループのしばっちといいます。

今日はGoogle Kubernetes Engine(GKE)で動かしていたコンテナを、弊社のプライベートクラウド(Nyah Kubernetes Engine: 以下NKEと呼びます)上でも動作させるようにした話を書きます。

ここを作りました

ここを作りました

「と言ってもコンテナでしょ?パッと動かせば動いてくれるんじゃないの?」…そう思われるかもしれません。実際私もそう思っていました。

ただやってみるとおもしろいもので、GKEでできたことがNKEでは別の方法を考えなければならなかったり、GKEで実現しようとしたことが制限があってできなかったりで非常に学びがありました。別のものと比較することで、普段使っているものの特徴が把握できる、ということもあるでしょう。

この記事はKubernetesが好きな人、業務で取り扱っている人GKEの特徴に興味がある人に向けてお届けします。興味深い、と思っていただけると嬉しいです。

なぜマルチクラウド化したかったのか

特徴をお話しする前に、なぜマルチクラウド化したかったのか、の動機をお話しさせてください。

オンデマンドプリントサービスSUZURIでは、Tシャツをはじめとしたグッズに対して、アップロードした画像の変換サーバとして2種類が稼働しています。CoffeeScriptで作られている「Lens」、そして Lensの次世代版としてRustで作られている「Lens2」です。

Lens_Lens2構成図

Lens/Lens2構成図

さて「Lens」においては、すでにGKE / NKE のマルチクラウド環境で動作しており、主としてNKEを使いつつ、バックアップやセール時の負荷でスケールアウトする用途としてGKEも使っています。

この「主としてNKEを使いつつ」の部分はRoute53の重み付きラウンドロビンで実現しています(参考情報:Lensでの重み付きラウンドロビンについて以前自動制御する仕組みをAWSで構築したことを記事にしました)。マルチクラウドにすることで、

  • 普段のトラフィックはプライベートクラウドのNKEを使うことでコストを抑える
  • セールなどでトラフィック量が増えたときはスケールアウトしやすいGKEにオフロードする
  • NKEの冗長構成としてGKEを用意することで可用性を向上させる

と、コストを抑制しつつスケールしやすいという各クラウド環境のいいとこどりをしているわけです。

この前例があったため、Lens2も同様の効果を期待してマルチクラウド化に踏み切りました。特に最近はLens→Lens2への移行が進んでおり、重要性が増している上にトラフィックも増えてきてGKEのコスト上昇が無視できなくなってきたことで優先度を上げて対応する必要もでてきました。

マルチクラウドの手法

Lensという前例があったため、Lens2もほぼ同様の構成でかんたんにできるだろうと思っていました。結論として、この予想は半分当たり、半分外れました。

当たったことは2つに集約できます。ひとつは、Route53を用いて重み付きラウンドロビンを用いてマルチクラウドを用いる仕組みにできたことです。Lensと同様の考え方でマルチクラウドにできたので、構成把握の学習コストを抑えることができるメリットがあります。

Lens2もLens1と同様にRoute53で重み付きラウンドロビンを用いる構成にしました

Lens2もLens1と同様にRoute53で重み付きラウンドロビンを用いる構成にしました

もうひとつは、Lens2のコンテナイメージを変更することなく、NKE/GKEで同一のコンテナイメージを用いることができたことです。これは当然のことに見えますが、クラウドによってビルドするコンテナイメージが異なるような状況ですと運用コスト/開発コストが重くなってしまいマルチクラウドの恩恵が減ってしまいます。

さてこの2つの「かんたんにできたこと」ですが、文面にすると当たり前のことを書いているだけで、特に難しいところはなさそうに見えます。同じコンテナイメージが別のクラウドで動作する、それは言ってみれば「当たり前」のことです。ただ、その「当たり前」を達成するために、インフラのレイヤーでは考えることができてしまいました。それが前述した「半分外れた」部分です。

前置きが長くなりました。それでは、インフラのレイヤーではどのような問題が発生し、それをどのように解決していったのかをご紹介しましょう。

問題その1 -GKEのTLS終端の仕様により、NKE/GKEをまたいだ重み付きラウンドロビンの構成にできない

GKEでのLens2ではIngressでTLS終端していて、そのためにGoogle Cloud Platform(GCP)のManaged Certificateを使っています。これはGKEでTLS終端するためのごく標準的な構成です。

Lens2ではManaged Certificateを使っている

Lens2ではManaged Certificateを使っている

さてこれをこのままRoute53で重み付きラウンドロビンにしようとしたのですが、Managed CertificateではAレコードを用いてドメインの保有を認識していることがわかりました。普段はNKEを使う運用にするつもりなので、Aレコードを引くとNKE側のIngressのIPアドレスが引けてしまいます。つまり、重み付きラウンドロビンを使う上でManaged Certificateを使うことができないことがわかりました。

GKEのTLS終端の方法をNKEと同じcert-managerに変更した

このため、GKEでManaged Certificateを使うことをやめ、GKEのクラスタ上でcert-managerを構築してLet's Encryptで名前解決する方法に変更しました。cert-mamangerであれば、AWSのIAMで発行したアクセスキーをcert-manager側に設定することでRoute53にアクセスでき、ドメインの保有を認識することができます。このTLS終端はNKE側にLens2を構築した際も用いた方法なので、NKE/GKEでTLS終端の構成を合わせることができ、認知負荷が軽減するメリットもありました🙃

GKEのTLS終端をcert-managerに変更した

GKEのTLS終端をcert-managerに変更した

GKEクラスタにcert-managerを入れた後に、Ingressのアノテーションを以下のように変更することで、ManagerdCertificate→cert-managerへ更新しました。※Issuerなどの周辺設定は省略

 kind: Ingress
 metadata:
   name: lens2-ingress
   annotations:
     kubernetes.io/ingress.class: "gce"
-    networking.gke.io/managed-certificates: lens2-production-cert
+    cert-manager.io/issuer: lens2-letsencrypt
 spec:
   rules:

問題その2 - GKEではMemorystore for Redisを使用していたが、NKEにはRedisがない

Lens2はGKEではMemorystore for RedisというGCPにあるフルマネージドなRedisサービスを使用していました。Deploymentに以下のようにパラメータを設定していたのです。

        - name: REDIS_URL
          value: redis://10.0.0.75:6379

プライベートクラウドであるNKEには当然フルマネージドなRedisはないので、Redisにアクセスする手段を検討しました。

NKEにスタンドアロンなRedisを構築することで解決

当初、NKEにデプロイしたLens2からGKEのMemorystore for Redisにアクセスする方法ができれば良いなと思いましたが、仕様上別システムからのアクセスはできないことがわかりました。セキュリティ上の懸念やレイテンシの悪化による旨みのなさを考えると納得のできるところです。

そこでNKEにRedisを構築することにしました。Lens2がRedisに求める要件を調査すると、幸いなことにあまり厳しくなく、

  • 速度改善のためにデータの一時置き場として使っている
  • セッションデータのような消えるとサービス影響のあるようなデータは格納していない
  • データは再起動でたまに消えることは問題はないが永続化されていると嬉しい
  • 当然、冗長化は必要である

ということでした。

構成はフェイルオーバ構成が組めるredis-operatorredis-clusterを検討しましたが、単一のIPアドレスでアクセスしているPodの仕様を変えたくないことや、redis-clusterに対応しようとするとコード修正が入ることがわかったため見送り、最終的にはシンプルにRedisをStatefulSetで複数立てて、それぞれが永続ボリュームを持つがデータは共有しない、スタンドアロン構成にしました。

RedisはStatefulSetで構築しました

RedisはStatefulSetで構築しました

NKE側のPodではserviceを指定することにより、Redisのどれかを参照する構成にできました。

        - name: REDIS_URL
          value: redis://redis-svc.redis.svc.cluster.local:6379

また、パラメータ確認や動作検証は慎重におこないましたが、その話は弊個人ブログにまとめていますので、興味のある方は見ていただけると幸いです。

こうして、GKE/NKEでコンテナに渡すパラメータを変えるだけでそれぞれRedisにアクセスできる状態にできました。

問題その3 - Ingressの仕様が異なるため、アップロードサイズに制限がかかった

最後の問題はNKEのLens2のリリース後に、ファイルをアップロードしようとするとエラーになる、という形で発覚しました。

どうもアップロードサイズが2MBに制限されているような挙動に見えました。

Ingressのアノテーションでアップロードサイズの設定を拡張した

GKEとNKE、Ingressの構成は同じですが、Ingressのコントローラは違うものを使っています。GKEはGKE Ingressと呼ばれるマネージドなコントローラで、NKEはIngress コントローラとしてはオーソドックスなIngress NGINX Controllerです。このコントローラの違いにより今までは発生していなかったアップロードサイズの制限が発生していました。

そこでドキュメントに記載の通りIngressのアノテーションに設定を入れることでこの問題を回避しました。

   annotations:
     cert-manager.io/issuer: lens2-letsencrypt
+    nginx.ingress.kubernetes.io/proxy-body-size: 8m

まとめ

今回はとあるシステムをマルチクラウド化した際に遭遇したクラウドの差分と、どう対処したのかについてご紹介しました。

  • TLS終端の方法が違うので重み付きラウンドロビンができなくなった
    • GKEのTLS終端の方法をNKEと同じcert-managerに変更した
  • GKEでマネージドで運用していたRedisをNKEに構築する必要があった
    • NKEにもRedisを構築した
  • NKEのIngressの仕様によりアップロードサイズに制限が出た
    • Ingressのアノテーションでアップロードサイズの設定を拡張した

へぇ〜と思っていただけた部分があったら幸いです。

なお、プライベートクラウドにトラフィックを寄せることで、マルチクラウドにする前と比較して50%以上のGCPのコスト削減になったことを併せてお伝えします。これがわかったときはやってよかった、報われた気持ちになれました!

切り替え日を境にコストが削減されている

GKEの日次コスト。切り替え日を境にコストが削減されている。

Kubernetesは運用する上ではわりとわかっているつもりでしたが、マルチクラウド化することによって普段何気なく使っているものが実は特徴があったりするのがわかって楽しかったです。そしてこんなおもしろい経験が積めたのもまたGMOペパボならではと思っています。

まだまだやりたいことは尽きません。プラットフォームをもっと良い構成、もっと面白い構成にしていきます!