Kubernetesのモダンな活用法 - 設計メソッドと、Virtual Kubeletで実現するサーバーレス化を学ぼう

Kubernetesはここ数年で一気にユーザーを増やしたコンテナオーケストレーターですが、一般化にともない、その活用法も洗練されてきました。本稿では「The Twelve-Factor Appを援用したKubernetes設計」と「Virtual Kubeletを活用したKubernetesのサーバーレス化」という、比較的新しい2つの活用法を武井宜行さんが解説します。

Kubernetesのモダンな活用法 - 設計メソッドと、Virtual Kubeletで実現するサーバーレス化を学ぼう

こんにちは。サイオステクノロジー株式会社でエンジニアをしております武井宜行(タケイ・ノリユキ/ 1@noriyukitakeiと申します。本稿では、比較的新しいKubernetesの活用法とその実践を紹介します。

Kubernetesは、ご存じの通り、ここ数年で一気に認知度が向上したコンテナオーケストレーターであり、コンテナをプロダクション環境で動作させるための様々な機能(Rolling Updateや負荷分散など)を持ったオープンソースソフトウェアです。

Kubernetesはかなり進化のスピードが速く、取り巻く環境も常々変化しています。新しい活用法、といってもさまざまなトピックがあり迷うところですが、本稿では「The Twelve-Factor Appを援用したKubernetes設計」「Kubernetesのサーバーレス化」の2点を特筆すべきポイントとしてピックアップしたいと思います。

「The Twelve-Factor App」に基づく、Kubernetesシステム設計のポイント

まずはKubernetesによるシステム設計のポイントをお伝えします。最近では「The Twelve-Factor App」に従い、Kubernetesの特性を生かした設計が見られるようになってきました。

「The Twelve-Factor App」とは、Herokuのエンジニアによって提唱された、モダンなアプリケーションを開発するための12のベストプラクティスをまとめたものです。もともとKubernetesのために定められた指針ではありませんが、12のうちのいくつかは、Kubernetesの構築に欠かせない重要な要素が含まれており、多くのエンジニアに参照されています。本家サイトより、12の要素を以下に記載します。

  1. コードベース:バージョン管理されている1つのコードベースと複数のデプロイ
  2. 依存関係:依存関係を明示的に宣言し分離する
  3. 設定:設定を環境変数に格納する
  4. バックエンドサービス:バックエンドサービスをアタッチされたリソースとして扱う
  5. ビルド、リリース、実行:ビルド、リリース、実行の3つのステージを厳密に分離する
  6. プロセス:アプリケーションを1つもしくは複数のステートレスなプロセスとして実行する
  7. ポートバインディング:ポートバインディングを通してサービスを公開する
  8. 並行性:プロセスモデルによってスケールアウトする
  9. 廃棄容易性:高速な起動とグレースフルシャットダウンで堅牢性を最大化する
  10. 開発 / 本番一致:開発、ステージング、本番環境をできるだけ一致させた状態を保つ
  11. ログ:ログをイベントストリームとして扱う
  12. 管理プロセス:管理タスクを1回限りのプロセスとして実行する

上記12の中でも、Kubernetesの設計に特に欠かせないのは「設定」「プロセス」「廃棄容易性」「ログ」です。これら4項目について、具体的な事例を交えながら設計の勘どころを紹介します。

【設定】ビルドの手間を減らす、設定ファイルの配置

データベースの接続情報などの設定ファイルは、ソースコードを格納するリポジトリの中に保存するのではなく、環境変数で外部から与えることが望ましいです。もしソースコードに含めてしまうと環境ごとにビルドが必要になってしまうからです。例えば、ステージング用、プロダクション用にビルドが必要となり、これにデモ用など新しい環境が加わると、さらに新しいビルドが必要になってきます。さらに設定ファイルを修正するだけでもビルドが必要となるという憂き目にあってしまいます。
 
実際の運用では、以下の例のようにConfigMapやSecretなどに設定情報を保存し、マニフェストにて環境変数として渡します。

    spec:
      terminationGracePeriodSeconds: 40
      containers:
      - name: wordpress
        image: wordpress:php7.4
        ports:
        - containerPort: 80
        env:
          - name: WORDPRESS_DB_USER
            valueFrom:
              configMapKeyRef:
                name: wp-user
                key: WORDPRESS_DB_USER
          - name: WORDPRESS_DB_PASSWORD
            valueFrom:
              secretKeyRef:
                name: wp-secret
                key: WORDPRESS_DB_PASSWORD

【プロセス】Podをステートレスにし、ユーザビリティを確保する

KubernetesのPodは、スケールアウトやスケールイン、ハード障害やPodそのものの障害などによる自律復旧のため、生成や破棄を繰り返すことが前提となっています。このため、セッション情報やデータベースに格納するデータは、Podの外に永続化し、Pod自体はデータを持たない、いわゆるステートレスにするべきとされています。
 
仮にPod内部にセッション情報を持たせてしまうと、負荷の増減の影響でスケールアウト / スケールインし、ユーザーのリクエストが別のPodに振られると、認証のセッションが切れて突然ログイン画面が表示されるという事態になります。
 
実際の運用では、MySQLやRedisなどに永続化します。Azure Kubernetes Serviceの場合は、MySQLのマネージドサービスであるAzure Database for MySQL、RedisのマネージドサービスであるAzure Cache for Redisなどを使うことがあります。

2

【廃棄容易性】Podを安全に停止する

繰り返しになりますが、Podは生成や破棄を繰り返すことが前提となっています。よって破棄された後は即座に起動する必要があり、またグレースフルシャットダウンにより、サービスを止めることなくPodを起動・破棄することが要求されます。
 
運用での対策として、Podが高速に起動するために、Dockerリポジトリのイメージをできるだけ小さくするという方法が挙げられます。以下は、レイヤーの数を減らすために、できるだけRUNで実施するコマンドを結合したり、aptのキャッシュを削除するなどしてイメージの容量を削減している例です。

RUN apt-get update && apt-get install -y \
    php \
    apache2 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

また、Podを安全に停止する(グレースフルシャットダウン)ために、preStopコマンドを活用します。

KubernetesのPodを終了するシーケンスは以下のようになります。

  1. kubetctlが、Podを終了するためのリクエストをAPI Serverに送信する。
  2. kubeletが、Pod終了のリクエストをAPI Server経由で受け取り、Podの終了処理を開始する。
  3. 「サービスからPodを除外する処理」と「preStop+SIGTERMをPodに送信する」という2つの処理を同時に開始する。これらの処理は完全に非同期で行われる。
  4. 事前に定義したterminationGracePeriodSeconds秒以内に3の処理が終わらなかった場合、PodにSIGKILLが送信されて、強制的に終了される。

より具体的な事例として、Apacheをgracefulに停止する方法(ユーザーのリクエストを途中で切断することなく安全に停止する)を考えてみます。

先述のとおり最終的にはSIGTERMが送信されますが、ApacheはSIGTERMを受け取ると、ユーザーがリクエストを送受信している途中でも強制的にプロセスを終了し切断してしまいます。

これを防ぐために、preStopを使います。preStopを使うと、Podを終了する際の処理を定義でき、Apacheをgracefulにシャットダウンできるのです。

その一連の処理を図解すると以下の通りとなります。

3

最初に説明したように、Podは停止のリクエストを受け取ると、まずサービスから除外する処理を行い、該当のPodにリクエストが届かないようになります。

ただし、除外処理が終わる前にApacheのgraceful shutdownが始まってしまうと、ユーザーのリクエストが処理中に切断されてしまう恐れがあるので、除外処理が終わるであろう時間(約3秒程度)sleepします。

そして、サービス除外の処理によって、停止対象のPodにリクエストが振られなくなると、apachectl –k graceful-stopによって、Apacheはgracefulにシャットダウンされます。

そのあと30秒ほど待ちます。これは、Apacheのリクエストタイムアウトの設定が30秒だったと仮定した場合ですが、Apacheのgraceful shutdownは非同期で行われるので、ここで30秒待機しないと、ユーザーのリクエストが終わる前にSIGTERMが送信され、処理中に強制的に切断されてしまいます。

terminationGracePeriodSecondsについては、Podの終了処理がこの値で定義した秒数以内に終わらないとSIGKILLが送信され強制的に切断されるので、サービス除外の処理(約3秒)+ユーザーのリクエストタイムアウト(30秒)=33秒にプラスアルファして40秒としています。

この設定であればPodを安全に停止でき、廃棄容易性を実現できるのです。

これらの処理を実現するpreStopは以下のように定義します。

lifecycle:
  preStop:
    exec:
      command: ["sh", "-c", "sleep 3; apachectl -k graceful-stop; sleep 30"]

【ログ】Pod破棄に対応したログ出力

前項のとおり、Podは生成と破棄を繰り返すことを前提として設計するべきです。従来のように、ログを個別のファイルに書き込んで、ローテーション……といった処理では、Podが破棄されると同時にそのログも消失してしまいます。
 
よってPodからは標準出力にログを出力し、そのログはflunetdなどのログコレクターに収集、ストレージに出力したり、ログのビジュアライザーなどで閲覧・分析するといった運用がベストです。
 
具体例を説明します。Pod内のアプリケーションのログを標準出力に出力すると、ワーカーノードの/var/log/containers以下にログがファイルとして出力されます。そのログをDaemonSetのPodとして構築したfluentd(ログコレクター)で収集し、Azureのログ管理サービスであるApplication Insightsに送信するといった運用です。

4

Kubernetesの“真の”サーバーレス化ソリューション

エンジニアHubに会員登録すると
続きをお読みいただけます(無料)。
登録のメリット
  • すべての過去記事を読める
  • 過去のウェビナー動画を
    視聴できる
  • 企業やエージェントから
    スカウトが届く