今回の記事はコラムとなりますので気楽に読んでください。興味がなければ飛ばしてもらってもかまいません。
今までPythonプログラミングについて解説してきましたが、実際にどのようなものを作れるかということまでは理解できなかったかもしれません。ちょうど「ネジの締め方」や「釘の打ち方」といったものを学んでも「家をどのように作ればいいか」ということは想像できないのと同じで、細かいプログラムの文法などについて学んでも、実際にどうアプリケーションやサービスを作ればいいかということはわからないと思います。
そこで今回はPythonという話題を一旦忘れて、私が作ったソフトウェアの内部を説明することで、どのようにしてアプリケーションやサービスを開発するかについてお話ししていきます。最初に趣味で作ったちょっと派手なサービスについて話して、次に地味だけれども業務に利用されているアプリケーションについて取り扱います。
さまざまなインフラを組み合わせた音声合成サービス
開発の動機を明確にしよう
あるサービスやアプリケーションを作る場合、「なぜそれを作るか」という開発の動機が重要です。私は単に「作りたいから」ということで開発する場合が多いですが、仕事としてコードを書くのであればそれが業務に繋がることが重要かもしれませんね。巨大プロジェクトの末端作業員である場合でも、開発の動機や自分に期待されていることは理解しているべきです。まぁ、堅苦しい話はこれぐらいにして、さっそく実例についてお話していきましょう。
ひとつ目は完全に趣味で作ったサービスです。Web系の技術を学びたいという裏の目的もありましたが、純粋に作りたいから作ってみただけです。
人工音声を読み上げるアプリケーション「ゆくも」 |
触ってもらえればわかると思いますが、基本的には入力したテキストを人工音声にして再生や保存、シェアをするというサービスです。声の種類やスピードなども細かく設定することができ、主に「動画に声をあてる」「メッセージを人工音声で送る(遊び要素)」などといった使い方を想定しています。これはPythonで作ったわけではありませんが、PythonでWebベースのシステムを組むのであれば似たような構成となるはずです。
ゴールの設定と利用技術の選定
実際に開発を始めようと思ったら、次にやるべき作業は「アイデアの具体化」です。まず最初にどういうものを作るかということを具体的に決めないと、どのような技術を選定すべきか決められません。
今回開発したサービスでは、
- レスポンシブデザイン(PCでもスマホでも使える)を採用したWebサービスとする
- 内部的にREST(HTTPによる)を使用
- インフラは冗長性を確保し、スケールする設計とする
- 音声合成にはC#とそのライブラリを利用する
といったあたりを最初に考えました。ここまで考えると、上記を実現するためには何が必要かわかってきます。そこから具体的な設計ができ、利用する技術についても選定できるようになります。上記ゴールを実現するため、今回は以下のようなサービスのアーキテクチャとなりました。
左がユーザー(ブラウザ)側で、右がサーバー側になっています。インフラの冗長性を得るためにサーバー側は複数のサーバー(Reverse Proxy、App Server)から構成されており、一台のサーバーが落ちてもサービスが全断しないようにコンポーネントごとに冗長化されています。利用しているAWSやConoha(サーバーレンタル業者)自体のダウンだと障害は回避できませんが、私の運用でダウンする確率よりも圧倒的に低いと判断して今回は考慮から外しています。
すべてを自分で作らないという思想
上記の図にある負荷を分散するためのReverse Proxyと冗長性を確保するためのAWSはプログラミングというより「インフラの技術」です。これらの機能を自力でつくりあげることは通常できません。すでにあるものをもう一度自分で開発するという「車輪の再開発」と呼ばれている行為は避けたほうが無難です。
大事なのは、
- 自分でサービスの1から100までをすべて作るというのは間違いで、プログラムは必要最小限にする
- プログラムを書かずに「何かを利用する」ことで同じものを実現できないか考える
ことです。たとえばReverse Proxy機能を持つサーバーを自分で開発することもできますが、より優秀なNGINXというサーバーがあれば、それを利用すべきです。AWSのDNSなどはプログラムを書けたとしても、それを動かす箇所が特殊な位置にあるので、個人での実現は不可能に近いです。
既存の技術を利用するにはプログラミング以外の知識が必要となります。すべてを自分で解決できるようになる必要はまったくないとは思いますが、基礎的なコンピュータサイエンスやIT業務(自分の担当範囲外)がどういうものかを知っておくと、とりあえず検索すれば解決の糸口を見つけられるようになると思います。したがって、浅くてもよいので守備範囲を広げることは、ITエンジニアとして大事な自分への投資だといえるでしょう。
先人の知恵を習う
プログラムに慣れてくると簡単な仕組みのものであれば、ある程度自由に作ることが可能になります。これが初心者が中級者になるということだと私は思っています。ただ、その「なんでも自由に作る」というスタイルを継続し続けると、おそらくその人が書いたコードはどんどん「カオス」になっていきます。「あれをするためにはこうすればいい……」と、行き当たりばったりで対応すると、コードの規模が大きくなるにつれ指数関数的にコードの複雑さが増してくるのです。
そこで役に立ってくるのが先人の知恵です。プログラミングの世界では何かを実現したいときに「定石」というべき解法が、かなりの数で存在しています。たとえば、複雑に絡まり合うコンポーネント間で連携をとるための「デザインパターン」や、汚いコードをきれいにするための「リファクタリング」がその代表格です。
コーディング技術以外にも先人の知恵はあります。たとえば今回のサービスだと内部的に「REST」を利用したことや、AWSおよびReverse Proxyによる冗長化手法あたりも先人の知恵です。また、レスポンシブデザインという概念や、それを実現するJavaScriptのライブラリ「BootStrap」の利用なども先人の知恵です。
繰り返しとなりますが、これらの既存技術は最初から使いこなせる必要はありません。ただ、「そういうものが存在する」ということを嗅ぎつけられる嗅覚は必要です。そしてその嗅覚を作るのは、日々の情報収集と基礎のコンピュータサイエンスの知識かもしれません。たとえばキャッシュやロードバランサの概念を知らなければNGINXのReverse Proxyを使うという発想になりませんし、DNSとDNSの弱点(名前解決したサーバーが死んでいたらどこにもいけない)を知っていないと、AWSを使った名前解決の冗長化という解決手法にまで辿りつけないでしょう。
音声合成サービスのアーキテクチャの解説
せっかく紹介したサービスなので、ひたすらどういう技術を使っているか列挙して説明してみます。プログラミング以外に興味がない人は読み飛ばしてしまってください。
クライアントとサーバーの間の通信
サービスはクライアントとサーバーから成り立っており、その連携をさせるためには両者が理解可能なプロトコルを使う必要があります。今回はブラウザで動くサービスなので「IP、TCP、HTTP」は暗黙的に使われており、その上に実際のサービスで利用する「処理のリクエストとレスポンスのやりとりのルール」を定めます。ルールは好きに決めてよいのですが、ベストプラクティスと呼ばれるものがいくつか存在しており、今回はREST APIと呼ばれる手法に沿って実装をしています。
クライアント側の仕組み
クライアント側は「ブラウザに表示される見た目を作る」動作と「サーバーに処理を依頼する」動作、および「サーバーからの応答を処理する」という動作をします。これを実現するためにはサーバー側に「HTML/CSS/画像」といった静的なコンテンツ向けのファイルや「JavaScript」というブラウザ上で動くプログラムを配置することが必要です。クライアントがWebページにアクセスした際に、それらのファイルをダウンロードすることでそのページが利用可能となります。
このサービスのクライアント側では、
- HTML5/CSS3という新しいHTMLルール
- Bootstrapというデザインフレームワーク(レスポンシブデザイン向け)
- JavaScriptとjQueryおよびそのほかのJSライブラリ
といったあたりの開発技術を使っています。以下にクライアントとサーバー(詳細は後述)の関係を示します。
注目して欲しいのはクライアント側にREST APIのレイヤーが挟まっていることです。このような設計にすることで、REST APIのルールを変更した場合に、変更をサービス自体から隠蔽したり、新しいサービスを開始する際に、細かい仕様を意識する必要がなくなります。どのように分けるかといったことを決めるのがプログラマの腕の見せどころかもしれません。
サーバー側の仕組み
次はサーバー側の仕組みについてです。以下にブラウザからの処理のリクエストがサーバーでどのように扱われるかを順に説明していきますが、個人レベルでサーバーを作る場合は、単体のアプリケーションで十分だと思います。今回は私自身が業務レベルのシステムを構築することを目標としてサービスを作ったので「複数のサーバーが協調して仮想的な一台のサーバーとして振る舞う」ような設計になっています。
名前解決ブラウザに入力されたドメイン名からは直接サーバーにはたどり着けないので、DNSによるドメイン名からIPアドレスへの名前解決が必要です。ただ、ドメイン名と一台のサーバーのIPをマッピングしてしまうと、そのサーバーが停止した際に全サービスが停止してしまいます。そのため、このサービスではサーバーを冗長化し、「DNS Round Robin(RR)」と呼ばれる技術で「ひとつのドメイン名に対して複数のIPアドレスを返す」ことで一台のサーバーが停止してもサービスが止まらないようにしています。純粋なDNS RRは停止したサーバーのIPも返してしまいますが、今回は死活管理機能も加えて、サーバーが停止したらそのサーバーのIPアドレスをDNSが返さないようにしています。これを実現するための方法はいくつかありますが、今回はAWSの
- 「Route53」と呼ばれるDNSサービス
- 「Cloud Watch」と呼ばれる監視サービス
を使っています。
Reverse Proxy名前解決されたユーザーからのHTTP Requestは直接HTTPサーバーに届くのではなく、まず「ロードバランサ」となるReverse Proxyに届けるようにします(DNSが解決するIPをReverse Proxyにするだけ)。Reverse Proxyはそれを「実際にHTTPサーバーとして動くアプリケーション・サーバー(後述)」に転送します。なぜこのような面倒くさい手法をとるかというと、Reverse Proxy(ロードバランサ)を使うと主に以下のメリットが得られるためです。
- 複数のアプリケーション・サーバーを一台のサーバーに見せかけることが可能(クラスタリングでパフォーマンス向上ができる)
- 要求に対する応答をキャッシュすることで、同じ要求を低速なアプリケーション・サーバーに何度も処理させない(アプリケーションサーバーの負荷軽減)
- セキュリティやDDoSに対する防御をさせることができる(FireWall機能を果たす)
- HTTPの要求に応じてリクエストを転送するサーバーを変更できる(処理ごとに実サーバーを分離できる)
このサービスだと、Reverse Proxyのキャッシュヒット率を95と仮定した際の処理可能なリクエスト数はアプリケーション・サーバー単体の構成より1桁ほど高くなることが確認できています。今回はReverse ProxyにはLinux上で動くNGINXと呼ばれるサーバーを使っています。
上記の図でApp Serverがコアサービスを担当し、Docker(クジラのアイコン)がほかの小さなサービスをまとめて担当しています(金銭面の問題で現在停止中)。
アプリケーションサーバーReverse Proxyから送られてきたHTTP Requestを実際に処理するのはアプリケーション・サーバーの仕事です。一般のHTTPサーバーとアプリケーションサーバーの一番の大きな違いは、前者が「静的なファイル処理を専門」とするのに対して後者は「ビジネスロジックの処理を担当」することです。今回のサービスだと「テキストから音声を作成するという処理」をサーバーが行っていますが、これがビジネスロジックにあたります。具体的には「テキストを形態素解析し音声記号に変換。それをWAVE(波形)にし、MP3形式に圧縮し返す」という一連の処理をサーバーがしています。また、今回はアプリケーションサーバにHTTPサーバーの機能(静的なファイル処理)も実装しているので単体のHTTPサーバーは存在しません。サーバー側の技術的なポイントとしては、
- REST APIのサーバー側の機能を実装
- 重い1リクエストにより全体が遅れるのを防ぐためにリクエストを並列に処理する(Thread Pool)
- 低速なDisk IOを可能な限り減らすことでパフォーマンスを向上(CPUとメモリ以外を使わないようにする)
- Networkの帯域を可能な限り減らすことでクラウドのインフラの負荷を減らす(データ圧縮)
- アクセスやパフォーマンスの測定を通してサーバーの利用具合や攻撃の検知をする管理機能を実装する(Management)
- 設定ファイルでパラメータを調整できるようにする(パラメータをハードコートすると複数のサーバーで使えないため)
といったあたりを設計の柱としています。開発環境としては、
- C# (サーバー処理)
- C(テキスト関連や音声関連のコア処理)
を利用しています。
今回の記事はコラムとなりますので気楽に読んでください。興味がなければ飛ばしてもらってもかまいません。
今までPythonプログラミングについて解説してきましたが、実際にどのようなものを作れるかということまでは理解できなかったかもしれません。ちょうど「ネジの締め方」や「釘の打ち方」といったものを学んでも「家をどのように作ればいいか」ということは想像できないのと同じで、細かいプログラムの文法などについて学んでも、実際にどうアプリケーションやサービスを作ればいいかということはわからないと思います。
そこで今回はPythonという話題を一旦忘れて、私が作ったソフトウェアの内部を説明することで、どのようにしてアプリケーションやサービスを開発するかについてお話ししていきます。最初に趣味で作ったちょっと派手なサービスについて話して、次に地味だけれども業務に利用されているアプリケーションについて取り扱います。
さまざまなインフラを組み合わせた音声合成サービス
開発の動機を明確にしよう
あるサービスやアプリケーションを作る場合、「なぜそれを作るか」という開発の動機が重要です。私は単に「作りたいから」ということで開発する場合が多いですが、仕事としてコードを書くのであればそれが業務に繋がることが重要かもしれませんね。巨大プロジェクトの末端作業員である場合でも、開発の動機や自分に期待されていることは理解しているべきです。まぁ、堅苦しい話はこれぐらいにして、さっそく実例についてお話していきましょう。
ひとつ目は完全に趣味で作ったサービスです。Web系の技術を学びたいという裏の目的もありましたが、純粋に作りたいから作ってみただけです。
人工音声を読み上げるアプリケーション「ゆくも」 |
触ってもらえればわかると思いますが、基本的には入力したテキストを人工音声にして再生や保存、シェアをするというサービスです。声の種類やスピードなども細かく設定することができ、主に「動画に声をあてる」「メッセージを人工音声で送る(遊び要素)」などといった使い方を想定しています。これはPythonで作ったわけではありませんが、PythonでWebベースのシステムを組むのであれば似たような構成となるはずです。
ゴールの設定と利用技術の選定
実際に開発を始めようと思ったら、次にやるべき作業は「アイデアの具体化」です。まず最初にどういうものを作るかということを具体的に決めないと、どのような技術を選定すべきか決められません。
今回開発したサービスでは、
- レスポンシブデザイン(PCでもスマホでも使える)を採用したWebサービスとする
- 内部的にREST(HTTPによる)を使用
- インフラは冗長性を確保し、スケールする設計とする
- 音声合成にはC#とそのライブラリを利用する
といったあたりを最初に考えました。ここまで考えると、上記を実現するためには何が必要かわかってきます。そこから具体的な設計ができ、利用する技術についても選定できるようになります。上記ゴールを実現するため、今回は以下のようなサービスのアーキテクチャとなりました。
左がユーザー(ブラウザ)側で、右がサーバー側になっています。インフラの冗長性を得るためにサーバー側は複数のサーバー(Reverse Proxy、App Server)から構成されており、一台のサーバーが落ちてもサービスが全断しないようにコンポーネントごとに冗長化されています。利用しているAWSやConoha(サーバーレンタル業者)自体のダウンだと障害は回避できませんが、私の運用でダウンする確率よりも圧倒的に低いと判断して今回は考慮から外しています。
すべてを自分で作らないという思想
上記の図にある負荷を分散するためのReverse Proxyと冗長性を確保するためのAWSはプログラミングというより「インフラの技術」です。これらの機能を自力でつくりあげることは通常できません。すでにあるものをもう一度自分で開発するという「車輪の再開発」と呼ばれている行為は避けたほうが無難です。
大事なのは、
- 自分でサービスの1から100までをすべて作るというのは間違いで、プログラムは必要最小限にする
- プログラムを書かずに「何かを利用する」ことで同じものを実現できないか考える
ことです。たとえばReverse Proxy機能を持つサーバーを自分で開発することもできますが、より優秀なNGINXというサーバーがあれば、それを利用すべきです。AWSのDNSなどはプログラムを書けたとしても、それを動かす箇所が特殊な位置にあるので、個人での実現は不可能に近いです。
既存の技術を利用するにはプログラミング以外の知識が必要となります。すべてを自分で解決できるようになる必要はまったくないとは思いますが、基礎的なコンピュータサイエンスやIT業務(自分の担当範囲外)がどういうものかを知っておくと、とりあえず検索すれば解決の糸口を見つけられるようになると思います。したがって、浅くてもよいので守備範囲を広げることは、ITエンジニアとして大事な自分への投資だといえるでしょう。
先人の知恵を習う
プログラムに慣れてくると簡単な仕組みのものであれば、ある程度自由に作ることが可能になります。これが初心者が中級者になるということだと私は思っています。ただ、その「なんでも自由に作る」というスタイルを継続し続けると、おそらくその人が書いたコードはどんどん「カオス」になっていきます。「あれをするためにはこうすればいい……」と、行き当たりばったりで対応すると、コードの規模が大きくなるにつれ指数関数的にコードの複雑さが増してくるのです。
そこで役に立ってくるのが先人の知恵です。プログラミングの世界では何かを実現したいときに「定石」というべき解法が、かなりの数で存在しています。たとえば、複雑に絡まり合うコンポーネント間で連携をとるための「デザインパターン」や、汚いコードをきれいにするための「リファクタリング」がその代表格です。
コーディング技術以外にも先人の知恵はあります。たとえば今回のサービスだと内部的に「REST」を利用したことや、AWSおよびReverse Proxyによる冗長化手法あたりも先人の知恵です。また、レスポンシブデザインという概念や、それを実現するJavaScriptのライブラリ「BootStrap」の利用なども先人の知恵です。
繰り返しとなりますが、これらの既存技術は最初から使いこなせる必要はありません。ただ、「そういうものが存在する」ということを嗅ぎつけられる嗅覚は必要です。そしてその嗅覚を作るのは、日々の情報収集と基礎のコンピュータサイエンスの知識かもしれません。たとえばキャッシュやロードバランサの概念を知らなければNGINXのReverse Proxyを使うという発想になりませんし、DNSとDNSの弱点(名前解決したサーバーが死んでいたらどこにもいけない)を知っていないと、AWSを使った名前解決の冗長化という解決手法にまで辿りつけないでしょう。
音声合成サービスのアーキテクチャの解説
せっかく紹介したサービスなので、ひたすらどういう技術を使っているか列挙して説明してみます。プログラミング以外に興味がない人は読み飛ばしてしまってください。
クライアントとサーバーの間の通信
サービスはクライアントとサーバーから成り立っており、その連携をさせるためには両者が理解可能なプロトコルを使う必要があります。今回はブラウザで動くサービスなので「IP、TCP、HTTP」は暗黙的に使われており、その上に実際のサービスで利用する「処理のリクエストとレスポンスのやりとりのルール」を定めます。ルールは好きに決めてよいのですが、ベストプラクティスと呼ばれるものがいくつか存在しており、今回はREST APIと呼ばれる手法に沿って実装をしています。
クライアント側の仕組み
クライアント側は「ブラウザに表示される見た目を作る」動作と「サーバーに処理を依頼する」動作、および「サーバーからの応答を処理する」という動作をします。これを実現するためにはサーバー側に「HTML/CSS/画像」といった静的なコンテンツ向けのファイルや「JavaScript」というブラウザ上で動くプログラムを配置することが必要です。クライアントがWebページにアクセスした際に、それらのファイルをダウンロードすることでそのページが利用可能となります。
このサービスのクライアント側では、
- HTML5/CSS3という新しいHTMLルール
- Bootstrapというデザインフレームワーク(レスポンシブデザイン向け)
- JavaScriptとjQueryおよびそのほかのJSライブラリ
といったあたりの開発技術を使っています。以下にクライアントとサーバー(詳細は後述)の関係を示します。
注目して欲しいのはクライアント側にREST APIのレイヤーが挟まっていることです。このような設計にすることで、REST APIのルールを変更した場合に、変更をサービス自体から隠蔽したり、新しいサービスを開始する際に、細かい仕様を意識する必要がなくなります。どのように分けるかといったことを決めるのがプログラマの腕の見せどころかもしれません。
サーバー側の仕組み
次はサーバー側の仕組みについてです。以下にブラウザからの処理のリクエストがサーバーでどのように扱われるかを順に説明していきますが、個人レベルでサーバーを作る場合は、単体のアプリケーションで十分だと思います。今回は私自身が業務レベルのシステムを構築することを目標としてサービスを作ったので「複数のサーバーが協調して仮想的な一台のサーバーとして振る舞う」ような設計になっています。
名前解決ブラウザに入力されたドメイン名からは直接サーバーにはたどり着けないので、DNSによるドメイン名からIPアドレスへの名前解決が必要です。ただ、ドメイン名と一台のサーバーのIPをマッピングしてしまうと、そのサーバーが停止した際に全サービスが停止してしまいます。そのため、このサービスではサーバーを冗長化し、「DNS Round Robin(RR)」と呼ばれる技術で「ひとつのドメイン名に対して複数のIPアドレスを返す」ことで一台のサーバーが停止してもサービスが止まらないようにしています。純粋なDNS RRは停止したサーバーのIPも返してしまいますが、今回は死活管理機能も加えて、サーバーが停止したらそのサーバーのIPアドレスをDNSが返さないようにしています。これを実現するための方法はいくつかありますが、今回はAWSの
- 「Route53」と呼ばれるDNSサービス
- 「Cloud Watch」と呼ばれる監視サービス
を使っています。
Reverse Proxy名前解決されたユーザーからのHTTP Requestは直接HTTPサーバーに届くのではなく、まず「ロードバランサ」となるReverse Proxyに届けるようにします(DNSが解決するIPをReverse Proxyにするだけ)。Reverse Proxyはそれを「実際にHTTPサーバーとして動くアプリケーション・サーバー(後述)」に転送します。なぜこのような面倒くさい手法をとるかというと、Reverse Proxy(ロードバランサ)を使うと主に以下のメリットが得られるためです。
- 複数のアプリケーション・サーバーを一台のサーバーに見せかけることが可能(クラスタリングでパフォーマンス向上ができる)
- 要求に対する応答をキャッシュすることで、同じ要求を低速なアプリケーション・サーバーに何度も処理させない(アプリケーションサーバーの負荷軽減)
- セキュリティやDDoSに対する防御をさせることができる(FireWall機能を果たす)
- HTTPの要求に応じてリクエストを転送するサーバーを変更できる(処理ごとに実サーバーを分離できる)
このサービスだと、Reverse Proxyのキャッシュヒット率を95と仮定した際の処理可能なリクエスト数はアプリケーション・サーバー単体の構成より1桁ほど高くなることが確認できています。今回はReverse ProxyにはLinux上で動くNGINXと呼ばれるサーバーを使っています。
上記の図でApp Serverがコアサービスを担当し、Docker(クジラのアイコン)がほかの小さなサービスをまとめて担当しています(金銭面の問題で現在停止中)。
アプリケーションサーバーReverse Proxyから送られてきたHTTP Requestを実際に処理するのはアプリケーション・サーバーの仕事です。一般のHTTPサーバーとアプリケーションサーバーの一番の大きな違いは、前者が「静的なファイル処理を専門」とするのに対して後者は「ビジネスロジックの処理を担当」することです。今回のサービスだと「テキストから音声を作成するという処理」をサーバーが行っていますが、これがビジネスロジックにあたります。具体的には「テキストを形態素解析し音声記号に変換。それをWAVE(波形)にし、MP3形式に圧縮し返す」という一連の処理をサーバーがしています。また、今回はアプリケーションサーバにHTTPサーバーの機能(静的なファイル処理)も実装しているので単体のHTTPサーバーは存在しません。サーバー側の技術的なポイントとしては、
- REST APIのサーバー側の機能を実装
- 重い1リクエストにより全体が遅れるのを防ぐためにリクエストを並列に処理する(Thread Pool)
- 低速なDisk IOを可能な限り減らすことでパフォーマンスを向上(CPUとメモリ以外を使わないようにする)
- Networkの帯域を可能な限り減らすことでクラウドのインフラの負荷を減らす(データ圧縮)
- アクセスやパフォーマンスの測定を通してサーバーの利用具合や攻撃の検知をする管理機能を実装する(Management)
- 設定ファイルでパラメータを調整できるようにする(パラメータをハードコートすると複数のサーバーで使えないため)
といったあたりを設計の柱としています。開発環境としては、
- C# (サーバー処理)
- C(テキスト関連や音声関連のコア処理)
を利用しています。
C++/Qt5を使ったGUIアプリケーション開発例
開発事例の2つめです。こちらは先程のサービスと異なり、ひとつのプログラミング言語とGUIのライブラリでゴリゴリと書いたデスクトップアプリケーションです。Pythonで単独アプリケーションを作るのであれば、先の事例よりもこちらに近いものとなるかもしれません。下記ページに簡単な説明とダウンロードリンク、ソースコードを管理しているGitHubへのURLがあります。
ユーザは主にCiscoのglobalで働くテクニカルサポートのエンジニアと、日本のパートナー企業の一部となっています。たまに開発エンジニアがデバッグのために利用しているという話も聞きます。
現状の課題を知り、何を作るか決める
私の専門は開発ではなくネットワークです。それもかなり泥臭いバックオフィスでのトラブルシューティングを専門としており、そこでは機器の状態を示す巨大なログを解析することが仕事において重要な作業となります。その作業を楽にすることが今回の開発の動機付けとなりました。
私がトラブルシューティングを担当しているCiscoの「Nexus」と呼ばれるデータセンター向けのスイッチや、そのほか多数の機器の状態は、テキスト形式の「ログ」で確認します。RouterやSwitchなどの多くのCiscoの機器は「show tech」と呼ばれている「多数のコマンドをまとめて取るコマンド」が実装されている場合が多く、Ciscoのエンジニアはそれを確認することで機器の状態を調査しています。
このshow techは多数のコマンドをとるため、ログのどこにどのような情報があるかを把握している必要があります。ただ、非常に多くの情報を含むログから必要な情報を探すというのは、たとえ検索を使ったとしても熟練したエンジニアでなければ難しい作業となります。これが今回解決すべき「現状の課題」だといえます。
課題がわかれば、それを解決するための方法もすぐに思いつきます。今回は以下のようなものとしました。
- 巨大なログファイルを項目ごとに整理して、読みやすくする
- 1Gを超えるログファイルでも、解析と高速な検索を実行できる高いパフォーマンスが必要
- WindowsでもMacでもユニバーサルアプリケーション(OSに依存しない)とする
- 複数種の機器で類似の問題があるので、機器に依存しない汎用的なフレームワークを作る
利用する技術の選定とプロトタイプ作成による評価
次にやるべきことは、先の要件に基づいたサービスなりアプリケーションなりを組むための「技術の選定」となります。具体的には、今回はプログラミング言語とGUIのフレームワークを決める必要がありました。今回は、高いパフォーマンスが必要かつユニバーサルアプリという制約から、C++というプログラミング言語とQt5というGUIライブラリを利用するという結論に至りました。
なお、技術の選定の際にプロトタイプ(簡易版)を組んで技術評価をするのが有効なこともあります。JavaとJavaFXというGUIライブラリという組み合わせも利用する技術の候補のひとつであったため調査しましたが、大量のログを表示する際のパフォーマンスと、SQLiteを内部に組み込んでいないという理由で今回はボツとなっています。向き合うであろう技術的な難所を事前に想像できず、プロトタイプでテストなどをしていないと、いざ作り始めてから解決できない問題にぶち当たるという悲惨な目に会う可能性があるので気をつけてください。
ブループリントの作成
しっかりしたアプリケーションやサービスを作る場合は、技術の選定が完了したとしても、いきなりコードを書き出すということは普通しません。まずはそれを作成するにあたり、どのようなコンポーネントやパーツが必要かということを整理する必要があります。今回はGUIのアプリケーションなので、どのような見た目のパーツが必要で、それがどのような機能を持っているかを書き出します。
確かスケッチブックなどに絵を書いたような気がしますが、残っていなかったので、現在のアプリケーションの画像を張って、それをベースに話します。
これだけではわからないと思うので少し補足をします。この画面は、以下の機能を持っています。
- ログのカテゴリを2つのレベルに分けている。左側のタブが大まかなジャンルで上側のタブが細かいくくりとなっている
- 各タブにはある特定グループのログのみ記載されている
- 中央左側はログのコマンド名のみが抽出されて列挙されている
- 右側はコマンド名と中身が記載されていて、左側のコマンド名をクリックすると該当箇所までジャンプ可能
- 右上のテキスト欄はコマンドの検索。キーワードを入れるとマッチするコマンド名一覧が表示されジャンプ可能
ここまで書くと、どのようなアーキテクチャにすべきか自然とわかってきます。たとえばGUIのパーツは入れ子構造になっていて、それは以下の順序で組まれています。
|
---|
各パーツはそれぞれ役割を持っていて、たとえば「左側のサマリー表示欄のクリックを検出したらそれを右側のログ表示パーツにまで伝える」、「右側のログ表示パーツはクリックが伝達されると該当箇所までジャンプさせる」といった役割があります。こうして動作の大まかな動きをいくつも考えて、一つひとつ整理していくのがブループリントの作成作業になります。
UML(統一モデリング言語)やシーケンス図を書くといったことまでしないでも、適当に列挙するだけでもだいぶ違います。逆に細かいことは机上で考えるよりも、実装しながら調整するぐらいのほうが素直なアーキテクチャになってくれるような気がします。
アーキテクチャの設計
細かいパーツ内の変更は比較的簡単にできますが、パーツAとパーツBがどのようにして連携をとるかといった大きいレベルでの仕組みは一度作ってしまうと簡単には変更できません。先ほどのブループリントの話で言うと「左側と右側の内部的な動き」はそれぞれ簡単に変えることができるものの、「左側が右側にクリックのイベントを伝達する」という連携手法については気軽に変更できません。
これは行き当たりばったりでパーツ間の関係性を作ってしまうと、コンポーネントが増えるに連れてコードがどんどんカオス状態になっていき、理解できなくなってしまうことが多々あるからです。コンポーネントが10や20あり、それぞれの間で縦横無尽にお互いを使いあうと、ある機能がどうやって実現されているかを調べるのが非常に苦痛になってきます。
この問題を防ぐために、まずはフレームワークの選定、もしくはフレームワークに 近いレベルの「プログラミングのルールの策定」が必要になります。フレームワークも使ってみないとどういうものか理解しにくいのですが、たとえば以下のようなものを考えてみましょう。
図の左側が特になにも考えずにパーツ間の連携をさせた場合で、右側が連携を中央集権的にした場合です。左側はパーツの数が増えるにつれて連携が複雑になっていきます。すべてのコンポーネント同士が連携しあうわけではありませんが、理論的にはパーツ数をNとするとN×Nの組み合わせがあります。一方、右側は必ず中央の連携用のコンポーネントを経由するというルールがあるため、連携はN×1となります。コード量でいうとおそらく右側のほうが多くなりますが、複雑性は右側のほうがかなり少ないです。今回のアプリケーションも右側に近いアーキテクチャを採用しています。
このようにプログラムの組み方にある種の制約を加えることで、複雑性を減少することが可能となります。これがアーキテクチャの設計の肝だといえます。フレームワークは図の右側のようなアーキテクチャを強制するための仕組みだといえるかもしれません。「このフレームワークはこう動くから、それに沿った形でパーツを作ってね」と最初からいわれていて、その通りにパーツとなるプログラムを書くことで、サービスやアプリケーションが作られます。どのようなアーキテクチャとなっているかはフレームワークにより多種多様です。
フレームワークを使った場合、それに沿った形でしかプログラムが組めないので、できることに制約は生まれます。ただ、その制約を得る代わりにプログラムがカオスになることを防いだり、本来は100書かなければいけないところを10書くだけでサービスを作れるようになったりします。そのため、一般的な作りを少しだけカスタマイズしたものを作りたい場合は、フレームワークを利用することが多いです。
正しいアーキテクチャの設計ができるようになれば、もう立派な中級プログラマーです。大きなプロジェクトの一部を担当するよりも、小さくてもいいので自分でプロジェクトを回してみるとよい経験が得られるかもしれません。
過度な複雑性は回避する
先ほどの項目でも述べましたが、プログラムは「コードの量や機能の量」と「コードの複雑性」は線形ではなく指数関数的に増加する傾向があります。そのため、あれもこれもと欲張りになるのではなく必要最小限の機能に絞った開発のほうが問題なく行える可能性が高いです。
今回作成したサービスは「ログを分類して表示する」という汎用的な作りとなっています。そのため、大まかな作業としては
- ログを解析して分類する
- 分類されたログを表示する
という2段階に分かれています。
これをひとつのアプリケーションですべてをやらせるという設計もできますが、今回は解析用のアプリケーションと表示用のアプリケーションの2つに分ける形で開発をしました。以下に両者の関係を記します。
図左側の「Cisco Log Parser」は機器のログを解析し、「Cisco Log Viewer」で見るための特別なファイル(CLVファイル)を書き出します。右側のCisco Log ViewerはこのCLVデータを表示しています。このCLVファイルにも複雑性を減らすための工夫がされています。ビューワーでカテゴリ化してログを表示するためには、ログ内部の各コマンドの分類情報などが必要です。
そのような情報を表現するためのデータ構造を自分で考えて、ファイルに対して書き込みや読み込みをするという実装方法も考えられますが、それはかなり複雑な仕組みとなります。今回はその問題を防ぐためにSQLiteという組み込みデータベースのファイルをCLVファイルとしています。
Cisco Log Parserはログを解析してSQLを経由してSQLiteに情報を書き込み、それをファイルとして出力します。Cisco Log ViewerはCisco Log Parserが作成したSQLiteのファイルを読み込み、そこから必要な情報をSQLを使って取り出します。
このような仕組みとすることで、
- 独自の読み込み書き込み処理の実装を避けて、複雑性を回避
- SQLiteに格納するデータを圧縮することでログの容量を1/10程度に減らす
- 検索などの機能をSQLite任せにすることができる
というメリットが得られています。要するにゴリゴリと自分でプログラムを書いて実装されるであろう機能をすべてデータベースにやらせているということです。実装をコードからデータベースに「移譲」することで複雑性を減らしています。せっかくなので、以下にCisco Log Parserの画像を記載します。
次回の内容
次回からまたPythonプログラミングに戻ります。以前に最重要な型を扱いましたが、次回はそれ以外の「実際のプログラムでよく利用される型」を扱います。
執筆者紹介伊藤裕一(ITO Yuichi)シスコシステムズでの業務と大学での研究活動でコンピュータネットワークに6年関わる。専門はL2/L3 Switching とデータセンター関連技術およびSDN。TACとしてシスコ顧客のテクニカルサポート業務に従事。社内向けのソフトウェア関連のトレーニングおよびデータセンタとSDN関係の外部講演なども行う。 もともと仮想ネットワーク関連技術の研究開発に従事していたこともあり、ネットワークだけでなくプログラミングやLinux関連技術にも精通。Cisco社内外向けのトラブルシューティングツールの開発や、趣味で音声合成処理のアプリケーションやサービスを開発。 Cisco CCIE R&S, Red Hat Certified Engineer, Oracle Java Gold,2009年度 IPA 未踏プロジェクト採択 詳細(英語)はこちら |
---|