Azure Kubernetes Serviceの3つの特徴

武井宣行氏:「Azure Kubernetes Serviceで実現する超低予算&(ほぼ)フルマネージド&本格的なWordPress環境」ということで登壇いたします。

私は武井と申します。サイオステクノロジーという会社で主にOSSのインテグレーションを行っています。

今回なんですけれども、Azure Kubernetesとはなんぞやというお話と、その活用方法としてWordPressをAKS(Azure Kubernetes Service)上に構築したお話をします。

ハッシュタグは「#jazug」、もしくは私のTwitterのアカウントで今載っているやつに、忌憚ないご意見をお願いいたします。

今回のセッションの話は、今回の移行対象になった弊社の技術ブログでも一応詳細のほうが載っていますので、そちらもあわせてご覧いただけたらと思います。

まず「Azure Kubernetes Serviceとは?」ということで説明します。

Kubernetesに関してはすごく有名なのでご存じかと思うんですけれども、じゃあAzure Kubernetesって何かというと、とても短時間で話しきれる内容ではないんですが、代表的なところで言うと大きく3つの特徴があります。

Masterサーバ・Nodeサーバを構築する必要がない。管理もすべておまかせですね。Kubernetes自身のバージョンアップもGUIで一発可能。あとからNodeの追加も可能。主にKubernetes、オンプレミスで構築するとけっこういろんな手間がかかると思うんですけれども、そのへんは全部Azureのほうで面倒を見てくれるサービスになります。

今出ているようにGUIで楽々環境構築。Kubernetesで実は一番大変なロギングもすでにサービスが用意されていたり、もし負荷が増大してNodeの追加が必要になったときもGUIで簡単に増減可能という、すばらしいサービスになっています。

けっこうKubernetesって進化が早くて、そういうようなKubernetes自身のバージョンアップもGUIで一発でできるサービスになっています。

VM2台立てて構築していた2つのブログを4ノードのクラスターへ移行

本セッションの概要に移ります。今回WordPressを移行したんですけれども、そもそもWordPressって何ぞやって話なんですが、基本的にはブログを提供するOSSのサービスで、以下のような構成になっています。

PHPでできていて、index.phpとかもろもろのファイルと、あとはwp-contentのところにいろんな画像とか投稿した記事とかのコンテンツが入ります。実際に画像以外のテキストデータ、投稿の内容はMySQLのデータベースに保存されます。

セッションの管理とかはSaltつきのCookieでやっていて、サーバ側はステートをもたない。全部Cookie側のほうでステートをもつかたちになっています。

Kubernetesの移行前の構成なんですけれど、弊社には技術ブログが2つありまして、それをAzure上のVirtual Machineにそれぞれ2台立てて2つ構築していました。

これをKubernetesに移したときがこんなイメージになります。ちょっと概要を述べると、Nodeが4台立って、フロントエンド用Nodeとバックエンド用Node。フロントエンド用Nodeとバックエンド用Nodeには、役割はそれぞれ違ったものをもたせていて、Azure Load Balancerで受けて、中のNGINX Ingressでそれぞれ割り振ります。

SendGridをメール送信のマネージドサービスとして使っていて、あとはファイル・画像などを格納するファイルサーバとして、VM上にNFSサーバを構築しています。データベースに関しては、AzureのマネージドのデータベースサービスのAzure Database for MySQLを使っています。

基本の構成は以下のとおりです。2つのブログを4ノードのクラスターへ移行しています。このあたりは先ほどお話したことが羅列してありますので、割愛します。

フロントエンド用Nodeとバックエンド用Nodeの振り分け

フロントエンド用Nodeとバックエンド用Nodeの振り分けというところなんですけれども、そもそもフロントエンド用Nodeとバックエンド用Nodeって何のために用意したかというと、フロントエンド用Nodeは、どちらかというと、ユーザーに表示させるブログの本当に静的なHTMLを配信するサーバ。バックエンド用Nodeは主に管理画面で、PHPがガリガリ動いて実際にブログを書く人たちが使う画面、で分けています。

こちらなんですけれども、1回Azure Load Balancerで受けて、L4でNGINXのIngressのPodに1回ロードバランスをして、L7のNGINXのIngressで、ホスト名とあとはロケーションですね、それで分けて各Nodeに振り分けていると。

例えばそのロケーションが「wp-admin」といった管理画面用のロケーションだったら、バックエンド用Nodeに割り振る。そうじゃなかったらフロントエンド用Nodeに割り振る処理をNGINXのIngress側で行っています。

それぞれフロントエンド用Nodeとバックエンド用Nodeでインスタンスのサイズを分けていて、フロントエンド用Nodeのプールは、静的なHTML参照なので、インスタンスサイズがけっこう低めの安いやつ。バックエンド用Nodeに関しては、インスタンスサイズがちょっと大きめの……というか、コンピュート優先のF2シリーズを使っています。

フロントエンド用Nodeの静的なHTMLの表示にはWordPressのキャッシュを使っていまして、これは1回表示すると静的なHTMLをキャッシュして、それ以降はPHP自身にアクセスをしないで、キャッシュされたHTMLファイルを表示するプラグインを使っています。

ここからがちょっとゴチャゴチャして複雑なところなんですけれども、バックエンド用Nodeとフロントエンド用Nodeの振り分けはどうやってやっているかというと、一応フロントエンド用Nodeとバックエンド用Nodeにそれぞれラベルをつけて、「env=user」「env=admin」というふうにつけています。

例えば「tech-lab.sios.jp」のブログページ用のPodを、さっき言ったuserというラベルがついたNodeに配置します。管理画面用のPodを今度はadminというラベルがついたNodeに配置します。というようなことをデプロイメントのリソースを使ってやっています。

実際にユーザーがアクセスすると、例えばアクセス先が「tech-lab.sios.jp/wp-admin」以外の場合は「tech-lab.sios.jp」ブログ用のNodePortにいって、そのNodePortからまたPodにアクセスして、フロントエンド用Nodeに割り振られるような、ここらへんちょっと複雑な処理をしています。

Load Balancerとかストレージとかそれぞれ各リソースの詳細

ここから詳細のほうを説明します。Load Balancerとかストレージとか、それぞれの各リソースの詳細を説明します。

今回お話するのは、このAzureのLoad BalancerとNGINXのIngressですね。

Load Balancerについては、一応NGINXのIngressでサービスにリクエストを振っています。NGINX IngressへのリクエストはパブリックIPアドレスを割り当てたAzure Load Balancerで行っています。NGINX Ingress側で、ホスト名と、あとはロケーションによってリクエストを割り振るPodを定義しています。

Azure Load BalancerとNGINXのIngressの両方とも、構築はHelmで一発で行っています。

NGINXのリソースを作成するマニフェストは以下のとおり。時間の関係で詳細な説明は割愛しますが、それぞれURLに基づいて割り振るNodePortを分けていたりしています。

一応SSL終端もNGINXのIngressでやっていますので、ここでワイルドカード証明書で入れて、URLの違った複数のブログを管理しているんですけれども、ワイルドカードの証明書1枚で管理していると。そのSecretリソースを最後のほうで定義しています。

Podの生成周りですね。この部分なんですけれども。

これちょっと特殊なんですけど、WordPressのDocker Imageって、docker-entrypoint.shが毎回起動時に実行されるんですけど、WordPressのプラグインを全部上書きして最初にインストールされたものにしちゃうような仕様なので、これをそういうふうにしないために、Dockerイメージを作り直してAzure Container Registryにpushしています。

KubernetesでRollingUpdateするのは実はけっこう大変

次にNode周りですね。

これはPod生成周りなんですけれども、ここでご説明するのは、RollingUpdateするための手法について書いてあります。KubernetesでRollingUpdateするのって、いろいろな設定をしないといけないので、実はけっこう大変だなと私は思っています。

Kubernetesって1回RollingUpdateするときに、まずpreStopという事前に定義したスクリプトというかシェルと、あとはそれぞれのPodにSIGTERMを送るといったような、2段階の処理に分かれています。

それが、事前に定義したterminationGracePeriodSeconds秒以内にそのpreStopとSIGTERMが行われないと、PodにSIGKILLが送信されてしまって、Podが強制終了されちゃう。なので、その前に適切にそれぞれのプロセスを落とす必要があります。

そのためにpreStopでApacheのgraceful-stopをやっていて、Apacheが落ちるのを、ユーザーのリクエストがちゃんと終了されてから落ちるようにgraceful-stopをしています。これをSIGTERMが送られる前に終わるようにしています。これらの処理がなんとかterminationGracePeriodSecondsが終わるまでにやるように設定をしています。

ここからは各Podを生成するためのdeployment.ymlについて説明します。ここも時間の関係上詳細は割愛しますけれども、ローリングアップデートをする際の次のPodの更新に移るまでの間隔とか、もろもろ指定しています。

先ほど言ったpreStopとSIGTERMの送信、ローリングアップデートに必要なもろもろの設定をここで行っています。

ここは主にSecret周りですね。WordPressはデータベースにつなぐためにいろんなパスワードとかの機密情報を扱うので、直接deployment.ymlに書くのではなくて、Secretリソースに登録をしてそこから取り出して、このdeployment.ymlでWordPressの起動に必要な環境変数に渡すことをやっています。

ここが先ほど言った適切にRollingUpdateするためのpreStopの処理で、sleep 3秒置いて、Apacheをgraceful-stopして、さらにsleep 30秒する処理を置いています。

バックエンドのDB接続確認のため、ヘルスチェックスクリプトは自前

一応ヘルスチェックについては自前のスクリプトを作っています。それについては後ほどご紹介します。ここで一番ポイントとなるのは、MySQLのSSL接続が安定するまでにちょっと時間かかるので、initialDelaySecondsというやつでヘルスチェックの開始を4分ほど遅らせています。

ここはreadinessProbeといってPodがサービスインする準備ができているかどうかをチェックするところで、ほとんど先ほどのヘルスチェックのところとは記述は一緒になります。

ここなんですけど、ここはWordPressの画像とかコンテンツを格納するためのNFSサーバにマウントするための定義を書いています。事前にNFSサーバを作っておいて、そこでNFSサーバで作成したボリュームをマウントして、subPathを切って、NFSサーバにボリューム上に作成されたディレクトリをマウントするといったかたちにしています。

ここは先ほどフロントエンド用とバックエンド用のNodeに割り振るための、このNode Affinityの設定をしていて、それぞれenv=userというラベルが付与されたノードにPodを割り振るスケジューリングを設定しています。

ここはヘルスチェックスクリプトです。WordPressって普通にTCPの80とか443を見るだけでもいいんですけど、バックエンドにDBがいるので、一応DBの接続までを確認するヘルスチェックスクリプトを自前で作りました。これが「select 1 as val」という本当に超簡単なSQLを発行して、それでSQLの結果が無事返ってきていたらステータスコード200、そうでない場合は500を返すかたちにします。

次にデータベース周りで、この部分ですね。

データベースについては、AzureのMySQLサービスの「Azure Database for MySQL」というマネージドのサービスを利用していて、それぞれのPodがこのマネージドなMySQLを見る構成になっています。

ストレージのレスポンスが遅い原因の特定にPrint デバッグしてみると……

次にストレージ周りなんですけれども、この部分ですね。WordPressのアップロードした画像とか動画とかを上げるところです。実はここがかなり一番苦労したところで、何が苦労したかをお話しますと……。

今回のストレージの構成は以下のとおりなんですけれども、それぞれのPodが、2つのブログを扱うので2つのディレクトリを切って、それぞれのPodがそれぞれのディレクトリにマウントしている構成になっています。

ここでAzureにはKubernetesから利用できる3つの共有ストレージがあります。1つはAzure FilesというSMBベースのマネージドサービス。もう1つはAzure NetApp FilesというNFSベースのマネージドサービス。あともう1つはVM上にNFS Serverをインストールしたものの3つがあって、今回はそれぞれ3つ全部試しました。

まずAzure Filesを試したのですが、これがすごい激オソで、1ページあたり8秒ぐらいのレスポンスで、かなりレスポンスが遅くて使いものにならなかった。

最初は原因がわからなくて「DBかな?」と思ったんですね。一応DBが問題かどうかを検知するスクリプトを作って動かしたんですけど、なんかDBはぱっとすぐ変えてくると。

次に、わからないので、BlackfireというPHPのプロファイラを使おうを思ったんですけど、エラーで動かなかった。原因はわからないんですけど、これは使えないなと。たぶん私の設定が悪いんですけど、エラーで動かない。

次に、もうやることなくなちゃったので、Print debugをするかたちにしました。もうこれ、怪しいと思われるところにerror_logでhoge1、hoge2、hoge3、hoge4って1行ずつ差し込むことをやりました。これが延々と10から20、30個ぐらい入れて、こんなかたちでPrint debugをしていって、どこがボトルネックになっているのかなというのをちょっとやってみました。

そこでやってみたらちょっと1個気づきがあって。この赤枠で囲っているところが処理にすごく異様に時間がかかっているところです。ここは何をしているのかなってソースコードを見たら、単純にWordPressのプラグインを読み取っているだけ。つまり、大量の小さいPHPファイルをバーってループでincludeさせるだけのところなんですね。でかいファイルとか大きい動画を読み込んでいるわけではない。

ここで「たぶんSMBがボトルネックになっているんじゃないのかな?」といったところを思いました。けっこうSMBって小さいファイルを読み込んだりするとけっこういろんな情報がくっつくので、それがまずいのかなと。

レスポンスは速くなったがリリース前日に問題発覚

というところで、実はこのAzure NetApp FilesというNFSのマネージドサービスに今度入れ替えました。これ入れ替えたらもうすごい見違えるように速くなったんですね。「やっぱりプロトコルの差かな?」と思って安心したんですけど、リリース前日に問題が発覚しました。

ポータルで見たら、4日で4万円ぐらい消費しているのがわかって、これはちょっとまずいということに。リリース前夜でもう夜23時ぐらい回ってたんですね。焦って夜中に、困ってますとService Requestを上げちゃったんですね。

そしたらなぜか海外からその直後に着信があって。あとから知ったんですけど、海外のMSサポートからだったらしいんですけど、ちょっと怖くて出られなくて。サポート言語を日本語にしたつもりだったんですけど、海外からかかってきてビビっちゃった。

でも、実はそのあとその直後にサポートの方からメールが来て、「電話かけたけどちょっと出られなかったみたいです」みたいな。「MSのサポートすげぇな」ちょっとステキすぎる。そこにはちゃんと原因が書いてあったんですね。これこれこういう原因ですよと。

それで内容が、Azure NetAppってこういう構成なんですけど、NetAppアカウントがあって、容量プールがあって、そこから容量プールからボリュームを切り出すと。私、そこの切り出したボリューム、これだったら、容量プール最初は4Tiで、そこからボリュームを100Gi切り出して、私この100Giに対して課金されると思ってたんですけど、実際聞いたら、海外のサポートの方が言うにはそうじゃないと。

実はここに課金されると。ということで、4TiBに課金されてたのですごいことになってたと。

というのがわかったので、どうしようかなと思ったところで、結局最後に行き着いたところは、VM上にNFSサーバをインストールしました。これでやっと本当に解決します。価格的な意味でも解決といったところで、めでたしめでたしということになりました。

ストレージ周りでVMを使っているので「(ほぼ)フルマージド」

最初のタイトルの「(ほぼ)」は、実はストレージ周りでVMを使っているので、完全なフルマネージドにはならなかったので、「(ほぼ)」とつけました。

気になる値段は、一応月額3万5,398円です。「これ本当に安いかな?」と思ったんですけど、これだけのNodeを動かしてこれだけ複雑な構成で、月額これぐらいなら、まぁいいかなと。自分の勉強代にもなりましたし。そんな感じですね。

最後にまとめます。

いろいろありましたが、無事稼働できてなによりでした。今回の気づきなんですけど、クラウドサービスの料金は注意しないと、意図しない課金がされちゃう。ストレージの選定は十分な検証をしてからにしましょう。あと、令和になってもプリントデバッグはやっぱりすごく役に立つなというのが今回の教訓でした。

そんな苦労をして移行した弊社技術ブログ、すみません、宣伝になっちゃうんですけれども、このURLで主にAzureの情報をたくさん提供していますので、ぜひご覧いただけたらと思います。

本当に最後に、このJapan Azure User GroupってAzureのユーザーが積極的に情報を交換し合うすごくすてきなコミュニティですので、みなさんもぜひ参加して情報を発信してみてください。

ご清聴ありがとうございました。

(会場拍手)