HTTP/2の話

HTTPSとHTTP/2について

WEBサイトはインターネットという通信技術の上に構築されています。コンテンツはサーバの上で、サーバはインフラの上でそれぞれ動作しますが、現在インフラ環境は大きな技術転換に直面しています。バックエンドのエンジニアだけでなくフロントエンドの制作者・ディレクターにおいても「知らなかった」が通用しなくなる前に基本を抑えておきましょう

HTTPS Requests

上のグラフは世界の上位1000サイトのHTTPSリクエストの割合を調べたもの。HTTPS(暗号化通信)による通信割合は2011年の時点で2~3%程度だったのが2014年あたりから急激に増加傾向となり、2018年現在で約70%にまで増加。WEBサイトの通信方式としてのデファクトスタンダードとなりつつある。

主な要因はそれまでユーザーが情報を入力するフォームパーツを含むページのみ暗号化していたものが静的なページにおいても暗号化して通信することを推奨する業界のトレンド。勿論Googleがサイトやページの評価として暗号化通信を実施していることを含めると言及したことも影響している。

過半数を超えたのは去年(2017年)のこと。2年前は約8割のリクエストが平文(従来型のHTTP)通信だったことを考えると、いかに急速にWEBサイトとの暗号化通信が普及しているかがわかる。(ただしトップ1000以降も含めた全体の普及率はまだ60%に過ぎない

HTTP/2について

HTTP/2はGoogleの提唱したSPDY(スピーディ)という通信方式(プロトコルといいます)を元に開発された方式で、2015年にIETF(インターネット技術タスクフォース)という機関がRFC7540として標準化。

従来のHTTP/1.1という通信方式よりも高速でマルチスレッドによる通信が可能。セキュリティが非常に高いなどの特徴がある。HTTPSの拡張仕様であるALPNという技術に依存しているのでHTTPS通信が必須。このことが普及の障壁となっていたが、近年HTTPS通信が普及したことによって急速に実装が進んでいる。

HTTP/2による通信はWEBサーバだけでなくWEBブラウザ側もHTTP/2に対応していることが必要。

主要ブラウザの対応状況は以下の通り

  • Google Chrome (v.31以降)
  • Mozilla Firefox (v.34以降)
  • Internet Explorer 11(Windows 10のみ)
  • Microsoft Edge
  • Opera
  • Safari (v.9以降)

所謂モダンブラウザは全て対応していますが、IEについてはWIN10上で動作しているv.11のみ利用可能となっています。
※ 余談ですがこういったMicrosoftの対応の遅さが普及を妨げていたのかもしれません

実際問題

まぁ、いいことばかりなんですけど実現には色々厳しい現実もあります。
例えばApacheでHTTP/2を使うにはpreforkじゃダメだとか、preforkがダメならmod_phpが使えねぇ…とか。

これらを解決するにはworkerまたはeventモードでApacheを動作させてmod_phpの代わりにFastCGIとかでPHPをCGI動作させるとか、諸々工夫が必要なんですね。

※ちなみにこのサーバはhttp/2で動かせています。努力と技術力の結晶だと自負してますw

なかなか厳しい道程ですが興味がある方は是非チャレンジしてみてほしいですね。

httpd.conf – MPM関連パラメータ

今回はApacheのprefork(mpm_event_module)の設定です。

おさらいしておくと、Apacheには動作モードが3つ(昔は2つだった)あって、

・prefork
・worker
・event

となってます。

現在のApache 2.4系のデフォルトは「event」です。
…と言われていますが、それはソースからmake installした場合の話です。

面倒だし特別な事情がなけりゃ普通はyumりますよね?
そんな時はpreforkでインストールされるみたいです。

Apacheの動作モードの違い

それぞれ何が違うかっていうとリクエストの捌き方が異なります。
preforkはシングルスレッドで動作します。(開発当初、世間のPCにマルチスレッドのプロセッサなんてほとんどなかったので当然といえば当然です)

workerとeventはマルチスレッドで動作するので効率もよく、サーバのCPUリソースも活用できるのですが色々と大人の事情でpreforkを使わざるを得ない状況も多々あります。

WEBサーバはApache本体だけで動いているわけじゃありません。様々なモジュールで機能拡張しています。例えばmod_phpとか。PHPが使えないWEBサーバって…厳しいですよね?しかし、mod_phpのmbstring系はシングルスレッドで動作します(というかマルチスレッドで動作しない)。なのでマルチバイト文字列をPHPで扱う場合なんかはprefork一択になってしまいます。

これを回避させるためにPHPをモジュール版(mod_php)ではなくCGIとして稼働させる手もありますが、その件については後日…。(ちなみにこのサーバはeventモード&CGI版PHPで動作してます)

とりあえずこれだけは設定しとく

preforkに話を戻すと、いくらシングルスレッドっていってもユーザーのアクセスは順番にリクエストされたりはしません。まとめて同時にきたりとか、普通にします。そこでApacheさんは同時にリクエストがきても大丈夫なように自分自身(httpdプロセス)をfork(クローンとか分身の術だと思えばいいです)して、受付係を何人も準備しておきます。マクドの注文カウンターみたいなもんです。

この注文カウンターに何人分の分身を配置するかの設定がhttpd.conf内のpreforkの設定です。
※今回は既にApacheがpreforkで動いてるものとして説明します。

主な設定項目は以下の通り。

ServerLimit

子プロセス数の上限(余談ですがeventやworkerの場合はCPUのコア数でいいかと思ってます)

StartServers

サーバ起動時の子プロセス数

MinSpareServers

アイドルな子プロセスの最小数(prefork用のディレクティブ?WorkerやEventではあんまり意味がなさそう)

MaxSpareServers

アイドルな子プロセスの最大数

MaxRequestWorkers

最大同時リクエスト数(2.2系ではMaxClientsというディレクティブでした)

MaxConnectionsPerChild

子プロセスが稼働中に扱うリクエスト数の上限(2.2系ではMaxRequestsPerChild)→「0」で上限なし

最低でもここにあげた項目はチェックと言うかhttpd.confに明示的に記述しておくべきです。
でないと後でサーバをメンテするエンジニアがサーバの設計根拠をイメージできません。

あと、たまに子プロセスとスレッドをごっちゃにして話をする猿人がいますが全然別物です。
さっきのマクドの話でいくと子プロセスってのはクローン店員のことで、スレッドっつーのはその店員が聖徳太子みたいに同時にどんだけの客を応対できるか…のことです。

私のワケのわかんない例えより、こっちの方が詳しくてわかりやすいと思うw
https://imokuri123.com/blog/2013/12/difference-between-process-and-thread.html

書いては見たものの

しかし、世間的にはGさんがhttp/2使えとか言ってるのでpreforkで動くApacheはどんどん減少するはずです。(→ 昨夏のセキュリティ対策リリース以降はmod_http2というモジュールがpreforkで使えないため)

通信パフォーマンスの観点からも今後はeventモードでApacheを動かすようにするべきだと思います。

Let’s Encryptで複数ドメインをまとめてSSL化するよ

certbot(Let's Encrypt)

某検索エンジンGさんがSSL(TLS)化されてないサイトをクソ扱いするらしいので面倒くせえけど対応します。

まずはCertbotっつーLet’s Encryptのクライアントをサーバにインストールします。

色々なサイトでCentOSならyumで一撃インストールだぜ!みたいなことを書いているけど、ウチのCentは32bitのOS7っつーありえない仕様なのでEPELのリポジトリが対応してません。

なのでここからは少し面倒だけど手作業です。
参考:https://letsencrypt.jp/usage/install-certbot.html

/optにLet’sEncrypt用のディレクトリを作って、そこにソースをオトします。
実行できるようにパーミッションも変更しておきましょう。

mkdir /opt/certbot
cd /opt/certbot
wget https://dl.eff.org/certbot-auto
chmod a+x certbot-auto

これだけで実行できるので

./certbot-auto

yumとかでインストールできる人は「certbot」ってコマンドらしいけど、ソースで入れた人は代わりに「PATH/certbot-auto」と読み替えてね、と公式ドキュメントにも書いてます。

初回起動時はpythonの実行環境構築や利用規約同意・メアドの要求などがされる。
どのドメインをSSL化する?みたいに聞かれるので「全部」と答えるw

Which names would you like to activate HTTPS for?
-------------------------------------------------------------------------------
1: XXXXXX.net
2: YYYY.XXXXXX.net
3: ZZZZ.XXXXXX.net
-------------------------------------------------------------------------------
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 1,2,3

運のいい人はここですんなりSSLキーが生成されるはずです。
勿論私は日頃の行いが悪いのでやはりと言うか、必然的にコケました。

Domain: XXXXXX.net
 Type: connection
 Detail: Fetching
 http://XXXXXX.net/.well-known/acme-challenge/XXXXXXXXXXXXXXXXXXX
 Timeout
To fix these errors, please make sure that your domain name was
 entered correctly and the DNS A/AAAA record(s) for that domain
 contain(s) the right IP address. Additionally, please check that
 your computer has a publicly routable IP address and that no
 firewalls are preventing the server from communicating with the
 client. If you're using the webroot plugin, you should also verify
 that you are serving files from the webroot path you provided.
 - Your account credentials have been saved in your Certbot
 configuration directory at /etc/letsencrypt. You should make a
 secure backup of this folder now. This configuration directory will
 also contain certificates and private keys obtained by Certbot so
 making regular backups of this folder is ideal.

certbotコマンドには色々オプションが有るんですが、
「何をしてもダメ。大人しく死ぬの」みたいな。。。

ドキュメントルートに「.well-known/acme-challenge/」というちょっとエロい名のフォルダが生成されて中のキーをLet’s EncryptのホストがHTTP経由で読みにくるみたいですが、そこがコケてるっぽいです。

ローカルネットワークは勿論外部からもブラウザでアクセスできているし、「そんなはずない」んだけど。

「色々試し」すぎると一定時間にリクエスト飛ばしすぎ、と怒られてアクセスできなくなります。そんな時は1時間ほど食事したり漫画読んだりして時間を潰しましょうw

さて、お腹も一杯になったので今一度ログを確認してみる。

less /var/log/letsencrypt/letsencrypt.log

あれ?IPv6で参照してる??
DNSでIPv6設定してねーわww
※ちなみにサーバ側ではOSのネットワーク設定で有効にさえしておけば賢いプログラムさん達が勝手にIPv4に読み替えてくれます。

念のためAAAAレコードもセットしてDNSを再設定&再チャレンジ。

 ./certbot-auto certonly --webroot -w /[ドキュメントルート1]/ -d XXXXXX.net -d YYYY.XXXXXX.net -w /[ドキュメントルート2]/ -d ZZZZ.XXXXXX.net

おお、通ったわw

上記コマンドの解説をすると、「certonly」はとりあえずSSLキーだけ作って的な。「-w」はドキュメントルートの指定で「-d」は(サブドメインを含む)ドメイン。今回のように1枚の証明書で複数のドメインに対応する場合は-wと-dのペアをスペース区切りで連記すればOK。また「foo.com」と「www.foo.com」みたいにwww有り無しを同じドキュメントルートで対応する場合は-wにセットする-dを上の例の「ドキュメントルート1」のように連記する。

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
 /etc/letsencrypt/live/foo.com/fullchain.pem
 Your key file has been saved at:
 /etc/letsencrypt/live/foo.com/privkey.pem
 Your cert will expire on 2018-05-25. To obtain a new or tweaked
 version of this certificate in the future, simply run certbot-auto
 again. To non-interactively renew *all* of your certificates, run
 "certbot-auto renew"

あとは生成されたキーをApacheに組み込みます。

/etc/letsencrypt/live/foo.com/にキーを保存したよ、とのことなので

<VirtualHost *:443>
 SSLEngine On
 DocumentRoot /var/www/www.foo.com
 ServerName foo.com
 ServerAlias www.foo.com
 SSLCertificateFile /etc/letsencrypt/live/foo.com/cert.pem
 SSLCertificateKeyFile /etc/letsencrypt/live/foo.com/privkey.pem
 SSLCertificateChainFile /etc/letsencrypt/live/foo.com/chain.pem
</VirtualHost>

やることはVirtualHostディレクティブに「SSLEngine On」を追記して「cert.pem」「privkey.pem」「chain.pem」の3つを指定。

FirefoxやChromeのURL補完は基本的に「http://」ベースみたいだし、今まで80番で運用していたWEBサーバを丸ごと443に引越しするならリダイレクトの記述も入れておいたほうがいい。

<VirtualHost *:80>
 DocumentRoot /var/www/nonHTTPS
 ServerName localhost
 RewriteEngine On
 RewriteCond %{HTTP_HOST} ^(.*)foo\.com
 RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>

こんな感じでOKかと。
SSLの設定が終わったら443ポートの穴あけとhttpdの再起動をお忘れなく。

Let’s Encryptの証明書は90日で有効期限が切れるのでcronで時々更新してやる必要がある。
以下のようなシェルを書いて、月1回キックしてやればいい。(十分に有効期限が残っている場合はスキップしてくれます)

#!/bin/sh
/opt/certbot/certbot-auto renew --post-hook "systemctl restart httpd"

「post-hook」オプションで実行後にApacheを再起動させてます。

以上、お疲れさん。

夜中にApacheが再起動?!

自宅鯖のhttpdのログを確認してると何もしていないのに午前3時頃再起動したような形跡が…。

以前root乗っ取られたことがあるんで、また支那の人かと思ったり疑心暗鬼だったんだけど…
犯人はログローテートです。

シェルを叩くとミドルのログローテート設定ファイルが確認できます

ls /etc/logrotate.d

/etc/logrotate.confのコメントを読む限り、どうやらrpm(yum)がインストール時に必要に応じて設定を落とし込むみたいです。

/etc/logrotate.dの中にいるhttpdの設定ファイルに以下のような記述がありんす。

postrotate
   /bin/systemctl reload httpd.service > /dev/null 2>/dev/null || true
endscript

ググって調べると、

postrotate~endscript
postrotateとendscriptの間に記述されたコマンドをログローテーション後に実行

prerotate~endscript
postrotateとendscriptの間に記述されたコマンドをログローテーション前に実行

ふむ。つーことは再起動じゃなくてリロードしてたんですね。
安心して眠れるわwww

※ちなみにCentOS6からはcronじゃなくてanacronでキックされます。

Top