しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure Cloud Services の移行先として Kubernetes + Windows Server Containers を検討してみる

既に Azure Cloud Services をバリバリ使って開発している場合は、そろそろ新しいサービスに乗り換えたいという思いを持っていることが多いのではないかと思います。

確かに Cloud Services は純粋な PaaS としての機能は十分ですが、CI/CD も組みにくいし RoleEnvironment など専用の処理になってしまうところが個人的には気に食わないです。なので Kubernetes と Windows Server Containers の組み合わせを移行先に出来ないか検討しました。

Kubernetes と Windows Server Containers については既に何回か検証しています。

ホスト OS の Windows Update や Windows Server Core の Docker Image のアップデートに関して少し難があるという判断をしていますが、それ以外は十分 Cloud Services を置き換えることが出来ると思います。

実際に検討した結果を以下にまとめます。Kubernetes と Windows Server Containers をバリバリ使わせてくれる仕事を募集しています。

Cloud Services vs Kubernetes

Cloud Services には Web Role と Worker Role の 2 種類が用意されていて、VIP スワップによって Blue-Green Deployment が行えるようになっています。最低限、この機能は実現する必要があります。

大雑把ですが、Cloud Services と Kubernetes の機能を比較しました。

Cloud Services Kubernetes
Web アプリケーション Web Role Deployment
バッチ処理 Worker Role Deployment / Cron Job
リソースの利用 1 アプリケーション 1 VM クラスタ内に任意の数を配置
ステージング環境 本番と同じ数の VM を用意 Deployment を個別に用意する
Blue-Green Deployment VIP スワップによる切り替え selector を使ったルーティング
デプロイにかかる時間 5-10 分ぐらい イメージが pull 済みであれば数秒
pull が必要な場合は 10 分ほど
CI/CD AppVeyor を使う*1 CI SaaS と kubectl を使って実現
スケーリング Azure Portal から変更 ACS の場合は Portal から変更
クラスタに余裕があれば Pod を増やす
監視 / アラート Azure に組み込み 別途用意する必要あり
OS アップデート Azure により自動 現在は手動
コンポーネントの追加 Startup Task で頑張る Docker Image をカスタマイズ

大体の機能が Kubernetes で実現できることが分かると思います。特にステージング環境に追加のコストが発生しない点や Startup Task でいろいろと頑張る必要がない点などメリットも多いです。

とはいえ、まだ Kubernetes の Windows サポート自体が Preview という課題はあります。Windows Server Containers 自体が立ち上がったばかりというのもありますが、早めに手を出しておくべき技術だと思います。

Kubernetes に 移行する方法

基本的な考え方として、Kubernetes では Deployment を使って Cloud Services の Web Role と Worker Role の機能を実現します。Replica Set が作成されるので、設定した数の Pod が動作することを保証できます。

Web Role と Worker Role の区別も Kubernetes としては違いがなく、IIS が動作しているかどうかぐらいです。その辺りは Docker Image を作成する時に違いが出ます。

Startup Task を Dockerfile に置き換える

Startup Task を使っていた場合には、予めバッチファイルとして作成していた Startup Task を Dockerfile 側で使うように変更しておく必要があります。

特殊な記述が無ければ、そのまま Dockerfile で実行する方法もあります。

FROM microsoft/iis

COPY Startup.cmd .
RUN Startup.cmd

これで Startup.cmd を実行済みの Docker Image が作成されます。移行としてはわかりやすい部類です。

Web Role の場合

Web Role から Kubernetes に移行する場合には、Cloud Services 向けの処理を置き換えや削除していくことになります。RoleEntryPoint などのクラスは必要ないので削除します。

要するに普通の ASP.NET アプリケーションにするように修正していくことになります。なのでデフォルトで参照されている Microsoft.WindowsAzure.* といったライブラリの参照は不要となります。

ASP.NET アプリケーションの場合は修正点は少ないと思いますが、Configuration 周りだけは RoleEnvironment.GetConfigurationSettingValue に依存していると思うので、ConfigurationManager と環境変数を使って設定できるように修正する必要があります。

一般的な IIS 向けの Dockerfile を使って ASP.NET アプリケーションをビルドすれば OK です。この辺りは Visual Studio 2017 の Docker サポート機能を利用しても良いかもしれません。

Worker Role の場合

Web Role と異なり Worker Role は Cloud Services の機能にべったりと依存していますが、やっていることは無限に動き続けるコンソールアプリケーションに近いので、RoleEntryPoint 周りをごっそりコンソールアプリケーションに移行すれば良いです。

以下は Visual Studio のテンプレートで生成される Worker Role のコードです。

public class WorkerRole : RoleEntryPoint
{
    private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false);

    public override void Run()
    {
        Trace.TraceInformation("WorkerRole1 is running");

        try
        {
            this.RunAsync(this.cancellationTokenSource.Token).Wait();
        }
        finally
        {
            this.runCompleteEvent.Set();
        }
    }

    private async Task RunAsync(CancellationToken cancellationToken)
    {
        // TODO: 次のロジックを自分で作成したロジックに置き換えてください。
        while (!cancellationToken.IsCancellationRequested)
        {
            Trace.TraceInformation("Working");
            await Task.Delay(1000);
        }
    }
}

OnStart / OnStop は長くなるので省略しましたが、いつの間にかに非同期対応になっていました。while で無限ループになっているのをコンソールアプリケーションに書き換えるのは簡単だと思います。

Kubernetes は Pod を停止する前に SIGTERM を送るので、それを処理することで Graceful Shutdown も行えるのですが、Windows の場合はよくわかりませんでした。

ビルドしたアプリケーションは以下のような Dockerfile を使ってビルドします。

FROM microsoft/dotnet-framework

COPY ConsoleApp.exe .

ENTRYPOINT ["ConsoleApp.exe"]

Docker Image のビルドは AppVeyor などで行わせるのが良いと思います。この Docker Image を Kubernetes にデプロイすると、Worker Role と同様に実行され続けます。

f:id:shiba-yan:20170621234403p:plain

標準出力に書き出すとログとして確認できるようになるので、結構シンプルに扱えます。

実行する Pod の数は Replica Set によって保証されているので、安心して使うことが出来ます。スケーリングも数秒で行えるので、かなり柔軟に使うことが出来ます。

Blue-Green Deployment

Kubernetes の標準では Blue-Green Deployment はサポートされていないですが、selector を使うことで任意の Pod へのルーティングが可能なので、それを使って切り替えを行います。

実現するためには Service 1 つと Deployment を 2 つ作成しておきます。作成するための YAML はそれぞれ以下のような形になります。

apiVersion: v1
kind: Service
metadata:
  name: webapp
  labels:
    app: webapp
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: webapp
    color: blue
  type: LoadBalancer
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: webapp-blue
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: webapp
        color: blue
    spec:
      containers:
      - name: aspnet
        image: shibayan/aspnet-demo:v1
        ports:
        - containerPort: 80
          name: http
      nodeSelector:
        beta.kubernetes.io/os: windows
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: webapp-green
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: webapp
        color: green
    spec:
      containers:
      - name: aspnet
        image: shibayan/aspnet-demo:v2
        ports:
        - containerPort: 80
          name: http
      nodeSelector:
        beta.kubernetes.io/os: windows

バージョンは Docker Image のタグとして表現しています。ラベルに含めても良いかなと思います。

この状態で Service を確認すると、ラベルとして color: blue を指定した Pod が関連付いていることが分かります。外部エンドポイントからのリクエストは、この color: blue の Pod にルーティングされます。

f:id:shiba-yan:20170621223614p:plain

ブラウザで外部エンドポイントにアクセスすると、v1 のアプリが表示されます。

f:id:shiba-yan:20170621234702p:plain

そこで Kubernetes のダッシュボードから Service の selector を変更してみます。今は color: blue となっていますが、次は green に切り替えたいので color: green に変更します。

f:id:shiba-yan:20170621223513p:plain

これでアップデートを行うと、即座に対象の Pod が切り替わっていることが分かります。

f:id:shiba-yan:20170621223623p:plain

同じようにブラウザで外部エンドポイントにアクセスすると、今度は v2 のアプリが表示されます。コネクションが切れるまでは古い方にルーティングされるみたいですが、しばらくすると切り替わります。

f:id:shiba-yan:20170621234837p:plain

実際には現在の Service が指している先を確認した後に、変更する Deployment を決めていけば Blue-Green Deployment を行うことが出来ますね。Cloud Services とは異なり、ステージング用に別のインスタンスを必要としないので効率的です。

App Service に移行すれば良いのではという声も聞こえてきそうですが、Worker Role の機能を Web Jobs で実現するのは地味に面倒だと思っています。個別にスケールさせることを考えると辛そうです。