FTPでアプリ間通信を実装するな馬鹿

タイトルのとおりです。

ここで言う「アプリ間通信」というのは、以下のようなものを想定しています:

システムAが定期的にシステムBにファイルをアップロードし、システムBはそれを定期的に読み取ってそれを元になにかやる・・・という感じ。

レガシーシステムでこういうことをやってるシステムというのは結構普通に見ます。私が体験した限りでは、新規案件でもレガシー資産へのインタフェースを残しておきたいのか、あるいは単に他の選択肢を知らないのか、FTPでデータのやりとりをしろという事を言う人が結構いる。おそらく、ネットワークプログラミングは知識が要るがファイル入出力ならばそれほど知識が要らないという安易な考えなのだろう。

はっきり言う。FTPはファイル送信プロトコルであって、アプリ間の通信に使うなんてのは害悪だらけでやるべきじゃない。以下、そう思う理由を列挙する。

イベント駆動ができない

ファイルがFTPによって送信されたタイミングで任意の処理をおこなうということができない。なので、常駐プログラム(サービスやデーモン)を作成し、周期的にファイルが有るかどうかチェックしなければならない。常駐プログラムを作るという作業がまずシステムの目的と反して本質的でない。加えて、常駐プログラムは普通のプログラムよりはプログラミング難易度が高い(メモリリーク、周期処理、エラーによる強制終了時の回復)。

送信側は受信側アプリケーションが正しくデータを受信したか判定できない

FTPでファイルが正しく送信されたかどうかをFTPサーバは教えてくれる。でもそれはFTPサーバーがデータを受け取って、ファイルをちゃんと保存したよ、というところまでを保証しているだけであり、そのあとそのファイルをアプリケーションが正しく読み取って正しく処理したかどうかまでは分からない。

もしこれを保証しなければならないような場面では、ファイルを送信した側のシステムへ正常に処理できたか、それとも失敗したのかを通知する処理を自分で書かなくてはならない。すると、その通知方法の実装をするにもステータスをファイルに書いて送り、送信元のシステムがFTPでそれを採取する、という処理になるのが自然だろう。だって送信時もFTPを使っているくらいなのだから。

こういう処理もシステムを実装する上で本質的でない作業に加え、とても面倒でバグの入り込みやすいプログラムになりがちだ。

FTPサーバ&クライアントは出来が悪いのが多い

本当に出来が悪いのが多い。WU-FTPDとか。正常に取得できなかったとしても教えてくれないとか、Aというソフトでは繋がるけど、Bというソフトではつながらないとか、ブラウザからはつながるけど、コマンドからやるとつながらないとか、そういう経験は誰にでもお有りだろう。

なにかファイルを落とす程度の理由で使うのであれば、もう一度チャレンジするとか、クライアントを変えてやってみるとか、そういう対応もできるだろうが、常時稼働するシステムではちょっと不安が残るところだ。

ファイヤーウォール設定が面倒くさい

FTPは使うポートが特殊だ。アクティブモードとパッシブモードという2つのモードが有ることからしてわかりにくい。パッシブモードまらまだ良いが、アクティブモードで通信することを前提としているレガシーシステムがたまにある。こういうのはすごく困る。なぜかというと、アクティブモードでは使うポートが変わるため、ファイヤーウォールはそれを見越した設定をしなくてはならない。具体的には、使うポートの範囲を決め、その範囲のポートを開けておく、というような操作をしなければならない。

元々、FTPというのは古いプロトコルで、開けるポートをしっかりと管理するような時代に作られたものではない。だからしょうがないっちゃしょうがないのだけど、現代に生きる我々がその古い事情に振り回される必要性も無い。

セキュリティ的な問題

素のFTPは平文でデータを送る。ユーザー名とパスワードも平文で送る。1000歩譲ってクローズドなシステムならまだ許せるが、インターネットを経由するようなシステムではこんなことは許されない。

さらに、前述の項目と重複するが、古いFTPサーバプログラムにはセキュリティホールが多い。レガシーシステムでちゃんとパッチをあてているようなものはほとんど無いと思われるので、これは大問題である。

ファイルを書き込んでいる時に排他ロックしないFTPサーバーがある

信じられないのだが、こういうのがある。かの有名なvsftpdもそうだ。こいつにはlock_upload_filesというそれっぽいオプションが有るのだが、これを設定しても共有ロックしかしてくれない。ということはつまり、ファイルを書いている途中で受け側のアプリケーションがファイルを読んでしまう可能性があるということである。私の経験上でも、こういうことは実際に何度かあって問題になった。

これに対処するために、たとえばファイルサイズをファイル名に含めるとか、ファイル書き込み中を示す一時ファイルを作るとか、ファイルの最後にチェックサムをつけるとか、そういうことを実装した上で、リトライ処理も実装する必要がある。くどいようだが、これもシステムの本質とは関係ない作業であることに加え、バグが発生しやすい処理だ。

システムの目的には本来関係無いようなプログラムを書くというのは、無駄な作業工数が増える、素人による車輪の再発明になりがち、バグを入り込ませて品質が低下する可能性が大きくなる、システムが複雑になることによる可用性の低下など、まさに百害あって一利なしである。

代替案

じゃあどうすりゃいいんじゃ、という人のために代替案を提示する。失礼を承知で言わせてもらうと、そもそも代替案を出せるくらいスキルのある人はこのページなんか見ないだろうから。

HTTP(S)で通信させる

HTTPベースの通信はかなりお手軽だ。デファクトスタンダードであるJSONなりXMLなり好きなフォーマットでやり取りを行えばよろしい。ほとんどのプログラムからHTTPは利用できる。イベント駆動も簡単にできる。常駐プログラムを書く必要もない。HTTPサーバやWebフレームワークなんて腐るほどある。受け側のプログラムが正常に処理したかどうかも簡単にステータスコード等で返せる。Webサーバを立てるのも簡単。設定も簡単。通信経路を暗号化したければHTTPSにすれば良い。ファイヤウォールやNATを超えるのもすごく簡単である。もし分からなくてもネットに腐るほど情報がある。

そもそもFTPを使うという選択肢を採用しているのは、SOCKET通信とか低レベルな処理を自分で書きたくないという理由であることがほとんど(である)気がするので、HTTPを使うというのはとても良い選択だとおもう。

SSHで標準入力からデータを流し込む

結構な力技だが、セキュリティ的にも問題ないし、プログラムの難易度も相当低く、お手軽だ。受け側のアプリケーションが処理した結果を受け取ることもできる。イベント駆動もできる。ただ、本来の使い方ではないので何か思いもしなかったような問題に悩まされる可能性はある。

SCP、SFTPでファイルを送る

いやいや、どうしてもファイルで送る必要がある。という場合はscpコマンドを叩くようにすれば良い。ただし、イベント駆動できない、常駐プログラムを作る必要があるなどのデメリットは解消されない。