macopy氏の自己紹介

macopy氏:よろしくお願いします。「デプロイ今昔物語~CGIからサーバーレスまで~」ということで、その(CGIからサーバーレスの)間にいろいろありますけれど、デプロイを次々とやっていって、みなさんを混乱させていくセッションになっています(笑)。

(話す)スピードが速いと思うので……。スピードというか、けっこう(内容を)ぎゅうぎゅうにしているので早口になっちゃうこともあるかもしれないですが、よろしくお願いします。ということで、話す内容としては「デプロイ」です。デプロイと言っても……。まあまあ(笑)。

自己紹介を先にします。面白法人カヤックのmacopyと申します。「Tonamel」のサーバーサイドテックリード(をしています)。Tonamelというサービスは後で5秒ほど説明します。(趣味で)Perlを書いていますが、仕事では今Goを書いています。自慢できることと聞かれたら、「ISUCONで優勝したこと」という感じで。ここは自慢します。(優勝したのは)2021年です(笑)。2022年は出題(をする側)でした。

いきなり広告ですが、Tonamelというeスポーツの大会をやるサービスを作っています。カヤックで作っているんですが、トーナメント表だったり、スイスドローというちょっと複雑な大会をやるとか、そういう(時に必要となる)システムをいろいろ手軽に組めるので、もし「大会をやってみたい」という方はググって使ってみてください。

セッションの概要と注意事項

広告はここまでです。Tonamelをこれから話すデプロイ技術の言葉で表すと、Pull型デプロイかつDockerを採用していると言えます。

という感じで、Pull型デプロイだったり、Dockerだったり。先ほど言ったCGIやmod_perlとかFastCGIとか、そういうテクニカルタームを説明していきます。これが実際にどういう技術で、どういうデプロイ方法を採っているのかを説明していくトークになっています。

ただ、ちょっと「あれ?」と思った方もいるかもしれません。このトークにおけるデプロイの範囲は、世間で言うデプロイの範囲よりもちょっと広く取っています。

Webアプリケーションを開発した後に、それをサーバーに上げて、さらにサーバーからどのようにしてリクエストが来たものをレスポンスで返すかとか。WebSocketとかもありますが、そういうサーブ、技術の話とかも含みます。厳密には違うけれど、デリバリーと言ったりもしますね。CD(Continuous Delivery)もありますが。そのあたりの技術も含みます。

例として、先ほど言ったプログラムの反映方法やホスティングの方法、プロセスをどう永続化させているかとかを含んだ話をしていきます。

あと先ほど言ったように、CGIとPull型デプロイを並列で語るのはちょっと乱暴な感じもしますが、そういう感じでやっていきます。

概ね時系列順、登場順に話していきますが、「この技術はやっているけどこの技術は紹介しない」みたいな取捨選択は時々あります。これは登壇者(macopy氏)の興味だったり、「あの時はこれを使っていたよな」という古い記憶を呼び起こして話そうかなと思っています。

あと、登壇者は中学生の頃にCGI(Common Gateway Interface)を用いたアプリケーションを使用していました。仕事ではPush型デプロイ、rsyncだったりから始めています。なので、仕事より前の部分に関しては、みなさんツッコミなどで教えてください。

先ほども話していましたが、まずは手法の解説をして、その後になぜこの技術が登場したのか、前の技術に対する優位性などの時代背景を紹介します。その後、環境を作っているので実際にデプロイをしていきます。そんな感じですね。やってみよう、体験してみようということで。やってみるのが一番わかりやすいんじゃないかなと思ってやっていきます。

あと、例えばApache 1.3とかを作り込むのは大変だったので、Apache 2.4を使っていたり。当時のCGIの場合はPerl 5.6とかが使われていたりするんですが(笑)。入れるのが大変だったので、system perlは5.32だったり、relocatable-perlは5.36を使っています。mod_perlも2系を使っています。Debianは最新ですね(笑)。bullseye 11を使っています。

途中で生FTPで通信する場面もありますが(笑)。その場合TailscaleでVPNを張っています。これも最新技術ではありますが、外部には公開されていないのでご了承ください。

というわけで、やっていくぞという感じですね。というわけでやっていきます!

CGIの手法

CGIです。(これが)どういう技術かというと、まずCGIというプログラムがあって、これがWebアプリケーションを構成しています。これは標準入力と環境変数で、HTTPリクエストが変換されたものを受け取ります。その後に、レスポンスとして標準出力を吐きます。stdoutで吐きます。その仲立ちをApacheがやっています。これがCGI(です)。

Apacheでやっている別のものだとH2OでもCGIは使えます。(スライドに記載された)このあたりのサーバーをとっかえひっかえできるように規格化されたものがCGIだと言える感じですね。

CGIの登場背景

CGIはこのトークで発表する一番古い技術で、1993年に登場というか提案されています。WWWTalkというメーリングリストを掘っていくと、NCSA HTTPdとかの開発をしている人たちのメールのやり取りを見ることができます。

NCSA HTTPdは後にApacheの元になったソフトウェアということになるんですが、これにhtbinという、外部からコマンドを呼び出してレスポンスを得る機能がありました。

HTTPリクエストに関しては、コマンドの引数に渡す形式でした。これをほかのサーバーにも使えるようにするために、環境変数や標準出力・入力を使うようにするかたちで、CGIというものが規格された。この時のバージョンは1.0です。

HTMLも策定当時は1993年だし、日本のWebもまだ商用化される以前の問題の話なので、このあたりの話はけっこう研究機関のメーリングリストに残っていたりします。

というわけで、(このあたりの話が)Webを取り巻くツールチェインが、まだ生まれる途中の黎明期という感じですね。

当時の「レンタルサーバー」という提供形態

あともう1つ僕が今から話すのは、レンタルサーバーを模した環境です。レンタルサーバーはどういう提供形態なのかというと、昔は仮想化環境というものはなかったし、Dockerとかコンテナもありませんでした。

なので、複数のユーザーに対して1つの物理サーバーの中でどのようにしてWebアプリケーションを集積するかという課題があった時に、サービスによりますが、homeディレクトリの中は「FTPで勝手にFTPでいじっていいですよ」という環境にしておいて、homeディレクトリ以下のpublic_htmlというディレクトリの中にHTMLだったりcgi_binというディレクトリができるので、ここにCGIプログラムを置いて、そこがCGIとして機能するとか。そういうかたちで一種のパースのようなものが、2000年代前後に提供されていた。今も提供されていますけれど。

これが一種のレンタルサーバーでした。そういうレンタルサーバーを模した環境で今からやります。

PerlでCGIを書いてデプロイ

というわけで、PerlのCGIをデプロイしていきます。まず、PerlのCGIのプログラムを読んでみましょう。

標準入力と標準出力と環境変数が読めればいいので、別にPerlに限った話ではありません。

(画面を示して)当時のコードを模した状態にはしているんですが、文字化けしていますね。なぜかというと、EUC-JPでデコーディングしているからです。

当時はUTF-8がなかったから(笑)。どんどんエミュレーションしていますが、EUC-JPで開くとこんな感じ。「こんにちはCGI」となっている。

CGIがどういうふうにHTTPレスポンスを返すかというと、最初にContent-Typeなんちゃらなんちゃらという感じで。sayなので標準出力ですが、MINEとかを渡してあげて。さらにヘッダー行を渡してあげて、1行空けて、そこからボディになる感じの規格でした。これでいけます。

かつ、これはEUC-JPで書いているからたぶん大丈夫ですが。昔はPerlでいうEncode.pmがなかったのでjcode.plがかなり広く使われていました。当時を模すため、試しにjcode.plを使っています。ただ、今jcode.plをそのまま使おうとすると、たしか5.24以降は問題が起こるので、(今回は)動くように改造して使っています。

あと最後に、先ほどCGIプログラムは「リクエストが来るたびにプログラムが起動して、リクエストが終わると落ちる」と説明をしたと思います。つまりpid=$$はコロコロ変わるはずなので、それ(が変化する様子)もやってみようかなと思っています。CGIスクリプトをデプロイしていきます。今は「Not Found」ですね。

当時はFTPがメチャクチャよく使われていたので、FTPでアップロードします(笑)。CGIディレクトリにhello.cgiという先ほど(行った)ものがありますね。まず先にjcode.plをアップロードします。その後にhello.cgiをアップロードしますが、この後にパーミッションを変更する必要がありますね。

これは実行権限、読み取り権限が同じユーザーがApacheを実行しているわけではないので、パーミッション調整が必要です。なので、jcode.plは644にしておいて、hello.cgiは755にしますかね。

こうするとこんな感じで動きます。「こんにちはCGI」と。

(会場拍手)

先ほど言ったように、リクエストが来るたびにプログラムが立ち上がったりしているので、pidがリロードするたびにこんな感じで変わります。ということで、CGIはこういう技術でした。

jqでCGIを書いてデプロイ

じゃあスライドに戻ります。標準ストリームと環境変数があればなんでもOKだし、言語非依存というのがあるんだけど、PerlでCGIというのは、せっかく言語非依存なのに、みなさんあまりにもありきたりだと思いませんか? 

このデモではつまんないかなと思ったので、(今度は)jqでCGIを書いてデプロイしようかなと思っています。jqというのは、JSONを標準入力で受けて、それをなんらかのかたちで加工して、JSONのかたちで返すという単体のプログラムです。これを標準入力・標準出力、あと環境変数も読めるので、jqでCGIプログラムは原理的に書けます。

というわけでやるんですが、cgi/hellojq.cgiになりますね。シンタックスハイライトが利かないですね、Vimさん(笑)。

defは普通に関数定義をしているんですが、なんとjqは関数定義ができるんですね。こんな感じで"Content-Type: text/html; charset=utf-8\n\n<p>こんにちは"とした後に改行を2つ入れる。先ほどと同じものですね。入れた後に「こんにちは」(となるの)で、今度はクエリストリングを読もうかなと思っています。

クエリストリングは、環境変数QUERY_STRINGに入るとCGIの規格上そうなっているのでQUERY_STRINGから取り出して、”name=(?<a>[^&]+)”でURLエンコードをする……。URLエンコードを実装するのは大変だったので、ここではASCIIしか対応していないですけど。この中に、nameパラメーターに入っているのを取り出してprintしようかなと思っています。じゃあこれをやっていきます。

まずhellojq.cgiですね。細かいかもしれないですが、その後にパーミッションを変更して。

jqの画面を読み込むと、こんな感じで出てくると。これはjqで動いています(笑)。

(会場拍手)

ない時はundefが入りますね。undefは自分で入れています。そんな感じです。じゃあスライドに戻りましょう。

CGIのデメリット

CGIのデメリットとして、プロセス起動のコストが重いという問題があるんですね。普通のCとか、すごく軽いプログラムだったらそんなに問題がないんだけれど、Perlみたいなインタプリタとかはけっこう大変な問題になっています。

かつ、でかいモジュールとかを読むとその分の起動コストがかかってしまうし、負荷もかかってしまう。ファイルディスクリプタとかの問題も出てくるだろうという感じですね。

昔はuseをあまり使わないCGIとかもあったけれど、Dynamic Loadingにして、必要がない時はロードしないことをがんばろうとrequireを使っているケースも昔はありました。

(スライドを示して)というわけでベンチを取ってみると、普通のApacheの静的エンドポイントでこれぐらいです。同じように10並列で1分間かけた感じのwrkでやったんですが、そうするとCGIだとこれぐらい差が出てしまう。

CGIはほかの技術に比べると、後になってきて重いとか遅いとか、そういう問題が起こるということが言われてきました。

(次回につづく)