はじめに
こんにちは、GMOアドマーケティングの星野です。今年のアドベントカレンダーも本日で最後となりました。
アドベントカレンダーを書いていると今年も終わりということを感じます。
GMOアドマーケティングではいくつかのプロダクトをGCPへと移行をしています。
移行では障害を起こさないことが優先されますが、最終的には単に移行するだけではなく、システムをクラウドネイティブな設計に変えていくことで、クラウドの恩恵を最大限に受けれるように改善していきたいと思ってます。
既存のシステムをクラウドネイティブな設計に変えていくにはコンテナ化が必要になります。 コンテナ化を行う際の設計指針の一つにコンテナ・デザインパターンというものがあります。
今回は、書籍の分散システムデザインパターン からいくつかのパターンを紹介したいと思います。
コンテナ・デザインパターンについて
コンテナ・デザインパターンには以下の3つに大きく分類できます
- シングルノードパターン
- マルチノードパターン
- バッチ処理パターン
シングルノードパターン
シングルノードパターンは1台のサーバで複数のコンテナを動かすことを想定したパターンです。1つのコンテナには1つの責務が原則ですが、既存のアプリケーションをコンテナ化しようとした際に、ロギングや監視など1台のサーバで複数の処理をしていることがほとんどだと思います。
その場合に、シングルノードパターンでコンテナを分割できないか検討をしてみてください。
アプリケーション自体が複数の処理をしてる場合も、シングルノードパターンを利用して別コンテナへの分割を検討できます。
シングルノードパターンを理解することで既存システムのコンテナ化やモノリシックなアプリケーションのコンテナ分割をするための手助けとなります。
マルチノードパターン
マルチノードパターンは複数台のサーバを協調させるためのパターンとなります。1つのコンテナには1つの責務に従って、コンテナ化をしていくと自然とマイクロサービス化していきます。マイクロサービス化により、アプリケーションはより疎結合となり、責任分界点がはっきりし、ソースコードもシンプルになります。
各アプリケーション間は定義されたAPIを介して通信することで動作しますが、アプリケーション毎にどうスケールさせるか、リカバリさせるかをアプリケーションに合わせた形で実現する必要があります。
マルチノードパターンを理解することでどういう戦略でスケールするか、リカバリさせるかを考える指針となります。
バッチ処理パターン
バッチ処理パターンは、バッチ処理を分割して、並列処理することでスケール、高速化するためのパターンになります。バッチ処理と言っても細かいバッチ処理を大量に実行したり、複数のバッチが協調して動作したり、大量データの処理を実行したりといろいろなパターンがあると思います。
バッチに応じて、大量に処理をするためにどう並列化させるか、協調させるためにどう待ち合わせ処理をするかを検討する必要があります。
バッチ処理パターンはバッチ処理を効率化、並列化、協調する際にどう設計するか検討する指針となります。
パターンを実現するためのシステムはクラウドのマネージドサービスにあるので、どのマネージドサービスを利用するのか選択する指針にもなります。
シングルノードパターン紹介
今回は、基本となるシングルノードパターンについて紹介していきます。サイドカー
メインのアプリケーションを拡張させたり、改善させたりする役割をもつコンテナをサイドカーコンテナと呼びます。そのため、1台のサーバ上で2つ以上のコンテナが動作することになります。
サイドカーコンテナはメインコンテナのアプリケーションを修正することが難しかったり、単純には拡張が難しい場合に利用されます。メインコンテナと協調して動作することもありますし、メインコンテナとは無関係で動作することもあります。
例えば、Nginxが動作しているサーバで、定期的に最新のHTMLソースを取得する必要が出てきた場合を考えます。
メインコンテナでNginxが動作しているとすると、メインコンテナではユーザからのリクエストを捌くことに集中させるため、同一サーバ内の別コンテナで、定期的にHTMLソースをダウンロードさせます。このようにサイドカーコンテナを利用することでコンテナの役割を分担することができます。
アンバサダ
アプリケーションコンテナと外部サービスとの間でやりとりをするコンテナのことをアンバサダコンテナと呼びます。アンバサダコンテナは外部サービスへの接続管理や再試行などを行います。
アプリケーションコンテナはアンバサダコンテナを通すことで、外部サービスへの接続先や、通信状況などを意識しないで実装をすることができます。
一度実装することで同じ外部サービスに対しては同じコンテナを再利用することもできます。
例えば、Redisのデータ量が増えてきたため、シャーディングによるスケールアウトをする場合のことを考えます。
シャーディングはキーをベースに行うとした場合に、その実装を既存のアプリケーションに組込むとすると、Redisへアクセスしているシステム全部に改修を加える必要が出てきます。
そこで、Redisへの接続及び、シャーディングロジックを実装したコンテナを作り、アプリケーションコンテナはそのコンテナと通信させることで既存のアプリケーションはRedisサーバが増えたことを意識することなく動作させることができます。
実際にRedisをシャーディングさせるとなった場合には、twemproxyを利用することになるでしょう
アンバサダパターンを利用する際は、サーキットブレーカ を意識した実装も検討する必要があります。
アダプタ
監視システムなどへの統一的なインターフェースを提供するコンテナをアダプタコンテナと呼びます。WebアプリケーションはDB、KVSなどが連携することで、動作します。
DBはMySQLやPostgreSQLなどのオープンソースソフトウェアを利用するでしょうし、KVSはRedisなどを利用するでしょう。RedisやMySQLは別々で開発されているため、当たり前ですが、統一されたインターフェースやログ出力は行われません。
バラバラのIFやログをアダプタコンテナを利用して統一的なインターフェースとすることが可能です。
例えば、監視ツールのPrometheusをMySQLに導入するとした場合のことを考えます。 PrometheusはmetricsAPIというインターフェースを通じて各コンテナから情報を取得しますがMySQLでは、当然Prometheus用のインターフェースは用意していません。
このような場合に、MySQL向けのPrometheus用のインターフェースを実装したアダプタコンテナを作ることで、MySQL側のコンテナを修正せずに対応を行うことができます。
ちなみに、MySQL向けのPrometheus用のインターフェースの実装は公式から提供されています。
サイドカーパターンの動作例
デザインパターンの理解を深めるために、サイドカーパターンの簡易的な実装をしてみます。前提条件として、minikubeのインストール もしくは Docker DesktopでKubernetesが有効になっている必要があります
今回実装するコンテナは以下の2つになります。
- メインコンテナ:静的ファイルを提供するNginx
- サイドカーコンテナ:静的ファイルを定期的に更新
サイドカーコンテナでは、5秒に1回
/var/log
ディレクトリにindex.htmlファイルを出力/更新します。出力内容はホスト名と更新回数、更新時間となります。
メインコンテナとサイドカーコンテナは共通のボリュームをマウントしており、サイドカーコンテナが更新したファイルをメインコンテナで参照することができます。
それでは実際に動作させてみましょう。
- 以下のYamlをmanifest.yamlとして保存してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-sidecar spec: selector: matchLabels: app: nginx replicas: 3 #レプリカ数 template: metadata: labels: app: nginx #Labelをnginxに spec: # 共有ボリュームを作成 volumes: - name: shared-logs emptyDir: {} containers: # サイドカーコンテナ # index.htmlを定期的に更新 - name: output-container image: alpine command: ["/bin/sh"] args: ["-c", 'i=0;while true; do echo "Runnning:${HOSTNAME}->count:${i}->$(date)" > /var/log/index.html;i=$((i+1));sleep 5;done'] # 書き込み先を共有のボリュームに volumeMounts: - name: shared-logs mountPath: /var/log # メインコンテナ - name: nginx-container image: nginx:latest ports: - containerPort: 80 # nginxの参照する静的コンテンツディレクトリを共有のボリュームに volumeMounts: - name: shared-logs mountPath: /usr/share/nginx/html --- apiVersion: v1 kind: Service metadata: name: sidecar-service spec: type: NodePort ports: - port: 80 nodePort: 30000 #フォワードするポート指定 selector: app: nginx |
- 続いて、manifest.yamlをデプロイします。
1 2 3 |
$ kubectl create -f manifest.yml deployment.apps/nginx-sidecar created service/sidecar-service created |
- デプロイの完了を待ちます。
READYが3/3
になるまで待ちましょう
1 2 3 |
$ kubectl get deploy -o wide NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR nginx-sidecar 0/3 3 0 7s output-container,nginx-container alpine,nginx:latest app=nginx |
- curlして動作を確認します
※ minikube使う場合はlocalhost
を$(minikube ip)
に変更してください
1 2 3 4 |
$ curl localhost:30000 Runnning:nginx-sidecar-554f4f8f67-pf456->count:118->Wed Dec 22 17:08:03 UTC 2021 $ curl localhost:30000 Runnning:nginx-sidecar-554f4f8f67-rbff7->count:118->Wed Dec 22 17:08:05 UTC 2021 |
まとめ
クラウドネイティブな設計の参考となるコンテナ・デザインパターンの概要と基本となるシングルノードパターンについて詳しく紹介をさせていただきました。自分の中でも今回のブログを通して考えが整理されたように思えます。
シングルノードパターンをベースにシステムをどうコンテナに分割していくのかを考えるきっかけとなってくれると嬉しく思います。
今回のブログを書くにあたり以下の書籍とサイトを参考にさせていただきました。
参考書籍
分散システムデザインパターン――コンテナを使ったスケーラブルなサービスの設計参考URL
- コンテナ・デザイン・パターンの論文要約
- Cloud Native Developers JP – ハンズオンチュートリアル
- https://docs.microsoft.com/ja-jp/azure/architecture/patterns/
- Kubernetes — Learn Sidecar Container Pattern
- コンテナデザインパターン
- コンテナのデザインパターンを学べる論文「Design patterns for container-based distributed systems」を読んだ
最後までお付き合いいただきありがとうございました!