はてなのエンジニア採用向け会社紹介資料を作りました

こんにちは。CTO の id:motemen です。
はてなでは現在、全方位的にエンジニアを積極採用中です!

採用活動の中で、はてなでのエンジニア職に興味を持っていただけた方々とお話ししていると、はてなという会社について知ってもらう機会を十分には作れていなかったと感じることが多くありました。

はてなという名前で「はてなブログ」「はてなブックマーク」というサービスを運営していることに比べ、多くの大手マンガサイトに導入される GigaViewer や、開発者向けの監視 SaaS である Macker…

管理画面からBigQueryを使ってサクセスする

ハッピーホリデー!id:cockscombです!!この記事ははてなエンジニアAdvent Calendarの8日目のエントリです。

サービス開発をしていると、限られた管理者のみがアクセスできる画面、つまり「管理画面」を作る必要に迫られます。管理画面では、データベースに登録されたさまざまな情報を検索、閲覧、編集できます。もちろんデータの扱いはプライバシーポリシーに則って、厳密に権限管理をしたり、操作ログを残したりします。

管理画面から情報を検索する

管理画面で、データベース上の情報を検索するとい…

HTMLのdialog要素とフォーム機能

こんにちは、id:nanto_viです。この記事ははてなエンジニアAdvent Calendarの1日目の分です。

Webアプリケーションでモーダルダイアログを実現しようとして苦戦したことはないでしょうか? 自前でHTML、CSS、JavaScriptを組み合わせて実装していくと、フォーカスやスクロールの制御が大変ですよね。そんな悩みを解決してくれるのがHTMLのdialog要素、Webブラウザ組み込みのモーダルダイアログ実装が利用できるという優れものです(モードレスダイアログとしても利用できます)。

dialog要素を使うことで、モーダルダイアログに要求されるJavaScript機能をブラウザが肩代わりしてくれるので、アクセシビリティの確保も簡単になります。

2021年12月現在、ChromeやEdgeはすでにdialog要素に対応しています。FirefoxやSafariの開発版でも対応が進んでおり、主要ブラウザでdialog要素を利用できる日も近いでしょう。

モーダルダイアログを表示する

モーダルダイアログを表示する場合、dialog要素に対するDOMオブジェクト(HTMLDialogElementオブジェクト)のshowModalメソッドを呼び出します。ダイアログを閉じる場合はcloseメソッドを呼び出します。

<p><button type="button" onclick="document.getElementById('ex-dialog-1').showModal()">詳細を表示する</button></p>
<dialog id="ex-dialog-1" aria-labelledby="ex-dialog-1-title">
  <h3 id="ex-dialog-1-title">詳細</h3>
  <p>詳細は○○です。</p>
  <p><button type="button" onclick="this.closest('dialog').close();">閉じる</button></p>
</dialog>

詳細

詳細は○○です。

お使いの環境はdialog要素に対応していないため、上の例でダイアログの内容がそのまま表示されています。

なお、showModalメソッドではなくshowメソッドを使うと、モードレスダイアログとして表示されます。

dialog要素とform要素を組み合わせる

ダイアログを閉じるのにHTMLのフォーム機能を使うこともできます。form要素のmethod属性にdialogという値を指定すると、フォーム送信の挙動が「送信先に移動する」のではなく「ダイアログを閉じる」になります(HTTP要求は発生しません)。

<p><button type="button" onclick="document.getElementById('ex-dialog-2').showModal()">詳細を表示する</button></p>
<dialog id="ex-dialog-2" aria-labelledby="ex-dialog-2-title">
  <form method="dialog">
    <h3 id="ex-dialog-2-title">詳細</h3>
    <p>詳細は○○です。</p>
    <p><button type="submit">閉じる</button></p>
  </form>
</dialog>

詳細

詳細は○○です。

HTMLのフォーム検証機能を使う

フォーム機能を使えるということは、フォーム検証機能も使えるということです。次の例では、入力欄に何か入力しないと「確定する」ボタンからダイアログを閉じられません。

<p><button type="button" onclick="document.getElementById('ex-dialog-3').showModal()">名前を入力する</button></p>
<dialog id="ex-dialog-3" aria-labelledby="ex-dialog-3-title">
  <form method="dialog">
    <h3 id="ex-dialog-3-title">名前を入力</h3>
    <p><label>名前: <input type="text" required></label></p>
    <p><button type="submit">確定する</button></p>
  </form>
</dialog>

名前を入力

なお、ブラウザによってはダイアログ上でEscキーを押下するとキャンセル操作として扱われ、何も入力していなくてもダイアログを閉じられます。

フォーム検証機能を迂回する

フォーム検証をせずにダイアログを閉じたいというときには、いくつか手段があります。

formnovalidate属性を使う

送信ボタンにformnovalidate属性を指定すると、フォーム検証を行わずにフォーム送信が実行されます。

<p><button type="button" onclick="document.getElementById('ex-dialog-4').showModal()">名前を入力する</button></p>
<dialog id="ex-dialog-4" aria-labelledby="ex-dialog-4-title">
  <form method="dialog">
    <h3 id="ex-dialog-4-title">名前を入力</h3>
    <p><label>名前: <input type="text" required></label></p>
    <p>
      <button type="submit" formnovalidate>キャンセル</button>
      <button type="submit">確定する</button>
    </p>
  </form>
</dialog>

名前を入力


この場合は送信ボタンの順序が重要になってきます。ブラウザによってはテキスト入力欄でEnterキーを押下したときに、最初に出現する送信ボタンが実行されたものとみなされるからです(暗黙的なフォーム送信)。上の例では「キャンセル」ボタンがデフォルトボタンとみなされ、「名前」入力欄で何も入力せずにEnterキーを押下したときもダイアログが閉じてしまいます。

ちなみに、個人的にはどの送信ボタンがデフォルトボタンなのか指定できるようになると嬉しいのですが、あまり議論は進んでいません。

closeメソッドを使う

「キャンセル」ボタンをデフォルトボタンにしたくなければ、送信ボタン(type="submit")ではなく汎用的なボタン(type="button")にするという手があります。この場合はJavaScriptでダイアログを閉じることになります。

<button type="button" onclick="this.closest('dialog').close();">キャンセル</button>
<button type="submit">確定する</button>

submitメソッドを使う

HTMLFormElementオブジェクトのsubmitメソッドを呼び出すことでも、フォーム検証をせずにフォーム送信できます。

<button type="button" onclick="this.form.submit();">キャンセル</button>
<button type="submit">確定する</button>

どのボタンが実行されたか判別する

送信ボタンを使ってダイアログを閉じた場合、その送信ボタンのvalueプロパティの値が、ダイアログのreturnValueプロパティの値として設定されます。

<p><button type="button" onclick="document.getElementById('ex-dialog-5').showModal()">名前を入力する</button></p>
<p id="ex-dialog-5-output"></p>
<dialog id="ex-dialog-5" aria-labelledby="ex-dialog-5-title"
    onclose="document.getElementById('ex-dialog-5-output').textContent = `「${this.returnValue}」ボタンが実行されました。`;">
  <form method="dialog">
    <h3 id="ex-dialog-5-title">名前を入力</h3>
    <p><label>名前: <input type="text" required></label></p>
    <p>
      <button type="submit" value="cancel" formnovalidate>キャンセル</button>
      <button type="submit" value="enter">確定する</button>
    </p>
  </form>
</dialog>

名前を入力


ダイアログのcloseメソッドの引数に値を指定すると、ダイアログを閉じることとreturnValueプロパティにその値を設定することが一度にできます。

<button type="button" value="cancel" onclick="this.closest('dialog').close(this.value);">キャンセル</button>
<button type="submit" value="enter">確定する</button>

終わりに

以上、HTML仕様とWebブラウザの進歩により、様々な機能を簡単に利用できるようになるという話でした。

はてなでは、Web技術を語らいサービスへの活用を探る仲間を募集しています。

hatenacorp.jp

バッチ処理における冪等性の検討 ─ クラウドネイティブもしくは、はてなダイアリーの自動移行を題材に

アプリケーションエンジニアのid:tkzwtksです。今回はバッチ処理の冪等性(べきとうせい、idempotence)について、どう考えるか/考えてきたかをご紹介します。

このエントリを書くきっかけとなったのは、はてなエンジニア有志で定期的に開催しているCloudNative推進会です。ここでは、社内のシステムをクラウドネイティブにしていくため「クラウドネイティブなシステムとはどういうものか?」を考えており、この会での「クラウドネイティブなバッチ処理」の議論も踏まえつつ説明していきます。

Hatena Engineer Seminar #17 をオンラインで開催しました #hatenatech

2021年11月25日(木)に、 Hatena Engineer Seminar #17 はてラボの裏側編をオンライン開催しました。ご参加いただいたみなさま、ありがとうございました。

このエントリーでは、当日のアーカイブ動画や公開資料をご紹介します。

Hatena Engineer Seminar #17 はてラボの裏側編 について

Hatena Engineer Seminar は、はてなのサービスを開発する上で、エンジニアがどのような事を考えているのか、どのような働き方をしているのかを語るイベントです。11月25日に開催した #17 では、はてなのラボサービス「はてラボ」にまつわる話題をお送りしました。

はてラボ」は「はてな社員の個人的アイデアに形を与え、未完成のサービスに改善を重ねながら本サービスに育てる」という目的で開始した、はてなの実験的サービス置き場です。本サービス化を目指すだけではなく、以下のような広義の実験的プロジェクトや実証実験の場としても使われています。

  • 稼働中の本サービスに導入することが難しい新規機能を先行して実装する
  • 本サービスとは異なるルールやポリシーに基づいてサービスを提供する
  • 本サービスとして継続提供することが難しくなったサービスを、規模を縮小した形で提供し、ラボならではの挑戦的な施策を導入し再生を図る

今回は、そんな「はてラボ」のサービスの中から、更新チェックツール「はてなアンテナ」、名前を隠して記事を投稿できる「はてな匿名ダイアリー」、ブックマークレットの作成・公開サービス「Hatena::Let」、「はてなブログ」で手書きの表現を手軽に楽しめる「てがきはてなブログ」の裏側について発表しました。

この記事では全4プログラムの概要や資料を紹介していきますが、配信のアーカイブ動画もYouTubeでご覧いただけます。動画の概要や以下の説明で、各トークの開始時間にもリンクしていますのでご利用ください。

発表概要と資料

「おもしろがり」からのサービスリリース 〜てがきはてなブログの場合〜(てがきはてなブログ)

「はてなブログ」を担当する、ブログユーザーチームのディレクター id:AirReader による発表です。

非エンジニアのディレクターの自由研究がラボサービスとしてリリースされるまでの軌跡についてお話ししました。

  • てがきはてなブログ
    • てがきはてなブログは、はてなブログで「手で書く」と「手で描く」表現を手軽に楽しめるサービスです。

発表資料を以下で公開しています。

配信アーカイブの該当部分は、1分36秒からです。

Hatena::Let の式年遷宮(Hatena::Let)

チーフエンジニア id:onk による発表です。

サービスを引き継ぐ際に Perl から Ruby に全て書き換えた裏側にあったソフトウェア式年遷宮の目論見と歴史についてお話ししました。

  • Hatena::Let
    • 2010年にラボサービスとしてリリースされた、ブックマークレットを手軽に作成・公開・共有できるサービスです。

発表資料を以下で公開しています。

配信アーカイブの該当部分は、18分26秒からです。

id:onk によるエントリです。
onk.hatenablog.jp

吉田を支える技術 (はてな匿名ダイアリー)

チーフエンジニア id:cockscomb による発表です。

2021年のエイプリルフール企画の発端や実現手段、リリースに至るまでの裏側についてお話ししました。

発表資料を以下で公開しています。

配信アーカイブの該当部分は、39分19秒からです。

はてなアンテナのクラウドジャーニー一合目(はてなアンテナ)

CTO id:motemen による発表です。

来年でサービス開始から20周年を迎えるはてなアンテナのクラウド移行の戦略やメンテナを引き継いだことのモチベーションについてお話ししました。

  • はてなアンテナ
    • 登録したページの更新情報がわかるアンテナサイト。2002年リリース。

発表資料を以下で公開しています。

配信アーカイブの該当部分は、54分01秒からです。

さいごに

ご参加いただいたみなさま、ありがとうございました。はてな技術グループでは引き続き、ブログやセミナーなどを通じた技術情報の発信に取り組んでまいります。

次回の Hatena Engineer Seminar にもご期待ください。

はてなでは新卒・中途、東京・京都を問わずエンジニアを募集しています。今回のセミナー内容に少しでも興味をお持ちなら、ぜひともご応募ください!

エンジニア採用 - 採用情報 - 株式会社はてな

認定スクラムマスター研修で獲得した知識をチームに還元できている話

こんにちは。Webアプリケーションエンジニアの“すてにゃん”こと id:stefafafan です。私は2021年7月に認定スクラムマスターの資格を取得し、現在はその知識をチームの仕事に活かしています。はてなにおけるスクラムの取り組みとしては、この開発ブログで id:shimobayashi が紹介した「すくすく開発会」や「プロジェクトテンプレート講義」があります。▶ はてなの開発プロ&#…

11月25日(木)に Hatena Engineer Seminar #17 はてラボの裏側編 をオンラインで開催します

こんにちは. はてなWebアプリケーションエンジニアの id:papix です.
このたび, 今年4月に開催して以来となるエンジニアセミナー, Hatena Engineer Seminar #17 はてラボの裏側編を,11月25日(木曜日)に開催することが決定いたしました!昨今の事情を鑑み, 今回もオンラインでの開催となりますが, アンテナや匿名ダイアリーなど, ラボサービスの裏側について聞くことができる貴重な内容となっています. 加えて, 今回は発表終了後に登壇者と歓談できる時間もご用意しており…

マルチテナント環境における Sentry のエラーグルーピングテクニック

マンガメディア開発チームの id:mizdra です。普段はWebアプリケーションエンジニアとして、マンガビューワ「GigaViewer」の開発に携わっています。GigaViewerの提供は2017年に始まり、執筆時点で12の出版社、14のサイトに導入いただいています。

GigaViewerでは、多数のマンガサイトを素早く構築するため、マルチテナントアーキテクチャを採用しています。データベースを始めとしてコードベースに至るまで、多くの部分をサイト間で共通化しています。

マルチテナントアーキテクチ…

数百万件残っていたHTTPのはてなブログを4年越しにすべてHTTPS化させた話

こんにちは id:cohalz です。はてなブログでは2021年4月の公式ブログで、すべてのブログをHTTPSに一本化していくことを案内しました。

「HTTPS配信」への切り替えと、ブログの表示の確認をお願いいたします

この時点でまだ数百万件のHTTPのブログが残っている状態でしたが、2021年8月には上記の案内に追記したように、全ブログでHTTPS化を完了できました。

完了までに行ってきたことをこの記事で振り返ってみようと思います。

はてなブログのHTTPS化のこれまで

はてなブログのHTTPS化は、2017年9月に最初のお知らせを行ってスタートしました。

当初の予定より時間がかかりましたが、2018年2月にHTTPS配信の提供を開始し、これ以降に作成されたブログは最初からHTTPSのみで配信されています。また、それ以前に作成されたブログでも、ユーザ側で設定を変更することで自分のブログをHTTPS化できるようにしていきました。

はてなが提供するドメインについては、2018年4月に対応を完了しました。2018年6月には独自ドメインのHTTPS配信が完了し、ここで開発も一段落となりました。

2018年までに取り組んだHTTPS化の詳細に関しては、当時の担当エンジニアのブログにまとまっています。

papix.hatenablog.com

papix.hatenablog.com

なぜこれまでHTTPS化を強制しなかったか

2018年までの取り組みでは、以前から存在するブログすべてにHTTPS化を強制したわけではなく、ユーザが自分で変更するオプトインの形式にしていました。

HTTPS化を強制しなかったのは、ユーザ側でHTTPの画像やスクリプトを配置している場合に、Mixed Contentが発生して表示が崩れてしまうおそれがあるためです。ユーザが必ず自分で表示を確認し、同意を得るという意味がありました。

Webブラウザの更新にともなってHTTPのブログに警告が表示されるなども起きていますが、問題があればユーザが個別にHTTPS化のボタンを押せば対応できました。このため、強制的に全ブログをHTTPS化する優先度はなかなか上がらない状況でした。

強制的にHTTPS化していくことにした経緯

そうした中、Webブラウザにおけるセキュリティやプライバシーの改善が進むにつれて、HTTPのブログではいくつかの機能が使えなくなり、ユーザからの問い合わせの頻度もどんどん増えていました。具体的には、ブログ上部の共通ヘッダーでログイン状態が取得できない、あるいははてなフォトライフに画像をアップロードできないなどの不具合が発生しました。

こうした不具合には対応できるものも対応できないものもあり、対応できる場合にもそのたび場合分けが必要となり、コードも複雑になってしまう状況でした。そういった対応は今後も増えていくことが予想されるので、さすがこれ以上は後回しにできないと判断し、強制的にHTTPS化していくことを決めました。

全ブログをHTTPS化するために必要だったこと

残った数百万のブログをHTTPS化するまでに、次のような作業を実施しました。

  1. 独自ドメインのHTTPS配信に関するモニタリングを強化する
  2. はてなドメインの証明書もLet’s Encryptに統一して処理を自動化する
  3. Mixed Content解消のためupgrade-insecure-requestsを導入する
  4. TLS接続のパフォーマンスを改善する
  5. 独自ドメインの検証対象にCAAレコードを含める
  6. HTTPS移行の速度を上げるためにキャッシュ削除で工夫する
  7. 移行するブログ数を把握するためBigQueryを活用する

この記事で順に説明していきたいと思います。

独自ドメインのHTTPS配信のモニタリング強化

HTTPSの接続を前提とするには、証明書が正しく発行され、正しく配信されていることに、より関心を向けていく必要があります。

はてなブログでは有料オプションの機能として、ユーザーが自分で取得したドメインを使用できるようにしています。そのため、独自ドメインのブログがたくさんあり、それぞれの証明書を適切に管理する必要があります。

証明書を正しく管理できているかを把握する必要性

これまでも証明書の発行に関しては、エラー率などある程度のモニタリングはできていました。しかし、発行に失敗したあと有効期限が切れるまで時間差があるため、発行エラーが後々の配信に影響を与えるかどうかまでは、そのタイミングで判断が難しい状態でした。

実際、利用者から「証明書の有効期限が切れた」という問い合わせをもらって、手動で再発行の対応をすることも何度かありました。

こうした状況で、証明書を正しく発行でき配信できているかを把握するため、下のシステム構成図の配信側(cert-cache-gw)でもモニタリングを強化することにしました。

▲ はてなブログのHTTPS化に関するbuilderscon tokyo 2018における発表資料

ポイントは以下の2つです。

  • リクエストのうち有効期限が切れてない証明書のどの程度の割合で返せているか
  • 期限を迎えた証明書を返してしまったドメインをすぐ把握するできるようにする

有効期限が切れてない証明書を返せた割合を確認

まず、Goのexpvarパッケージを使って、有効期限が切れた証明書を返した数などを内部でカウントするようにしました。

$ curl -s 127.0.0.1:8888/api/metrics/certs | jq .
{
  "cache.hits": 4,
  "cache.misses": 1,
  "cert.already_expired": 1,
  "cert.soon_to_be_expired": 0,
  "cert.valid": 4,
  "requests": 7,
  "status.bad_request": 0,
  "status.internal_server_error": 0,
  "status.not_found": 2,
  "status.ok": 5
}

そこで取得したJSONを、mackerel-plugin-jsonを使ってMackerelにメトリックとして投稿します。mackerel-plugin-jsonでは-diffオプションでカウンター値の1分間の差分を取ることができ、1分ごとのリクエスト数などをそのまま投稿できます。

これを元に、リクエストのうち有効期限が切れてしまった証明書の割合などを、よりリアルタイムにMackerel上でモニタリングできるようになりました。

f:id:cohalz:20211004001626p:plain
有効期限まで21日以上の証明書を返せた割合

mackerel-plugin-jsonに関しては次の記事も参照してください。

cohalz.co

有効期限を迎えた証明書を見つける

さらに証明書の有効期限が近かったり、有効期限を迎えてしまったブログをログに出力し、集計できるようにもしました。

これにより問い合わせがなくても、問題が起きたブログをすぐ発見できるようになりました。

証明書をLet’s Encryptに統一して自動化

Let’s Encryptは非営利団体のISRGが運営する認証局で、すべてのWebサイトに安全な接続を提供するため、2014年から証明書を無料で発行しています。

はてなブログでは、もともとLet’s Encryptの利用は独自ドメインのブログのみで、はてなで提供している*.hatenablog.com*.hatenadiary.jpといったドメインは商用の認証局が発行する有償の証明書を利用していました。

これを止めて、Let’s Encryptに統一しました。これは主に以下の2つの理由によります。

  • サポートする範囲の統一
  • 更新作業の自動化

サポートする範囲を統一する

証明書の発行元が異なると、*.hatenablog.comにはつながるが独自ドメインのブログにはつながらない(またはその逆)といった事態が起こりえます。そういった問題をできる限りなくしたい気持ちがありました。

独自ドメインのHTTPS配信にLet’s Encryptを使用しないという選択肢が現実的でない以上、独自ドメイン側に合わせる形ですべてのドメインがLet’s Encryptを利用するようにしました。

証明書の更新作業を自動化する

有償の証明書は購買から入れ替えまでのフローに多くの手順が必要で、自動化も難しい状態でした。その点、Let’s Encryptの証明書は自動更新を前提としており、次の記事で紹介しているようにはてなブログで利用する仕組みも社内に存在していたため、これに乗り替えました。

developer.hatenastaff.com

また、2018年3月以降には2年を超える証明書が発行できなくなり、2020年9月以降には1年を超える証明書が発行できなくなるなど、ここ数年で証明書の最長の有効期限がどんどん短くなっています。これによって更新が必要な頻度も増えたため、自動化のモチベーションが高まったこともあります。

2018年の初めに購入した3年間有効な証明書を使い続けていましたが、これが切れる前にすべてLet’s Encryptに入れ替え終わり、更新作業の必要もなくなりました。

CSP標準を利用してMixed Contentを解消

それまでHTTPだったブログにHTTPSで接続するようになると、読み込まれている画像やスクリプトによってMixed Contentが発生してしまう可能性があります。

はてなで提供する画像などの貼り付けは既にHTTPS対応していますが、ユーザが自由にスクリプトなどを貼り付けられる仕様上、強制的にHTTPS化するとブログの表示が急に崩れてしまうことが数多く発生してしまうと予想されました。

upgrade-insecure-requestsを指定したい

これを回避するため、HTTPのCSP(Content Security Policy)標準には、upgrade-insecure-requestsという仕組みが存在します。

これを有効にするとMixed Contentは解消できますが、代わりにHTTPでしか配信されていないリソース(画像なども含む)が読み込めなくなるという大きなデメリットもあります。2018年当時はまだHTTPSで配信されてないサイトも存在しており、はてなブログ一括で有効にすることは諦め、案内を出すに留めたという経緯もありました(スクリーンショットは当時のレビュー)。

f:id:cohalz:20211004001513p:plain
2018年当時の議論

しかし、多くのMixed Contentが解消されるメリットはかなり大きいため、3年が経過した2021年に再び検討することになりました。

Content-Security-Policy-Report-Onlyによる検証

検討のため、Content-Security-Policyヘッダでupgrade-insecure-requestsを指定する代わりにContent-Security-Policy-Report-Onlyヘッダを利用し、Mixed Contentが発生したブログや読み込むリソースのURLを送信して、そのログを可視化してみました。

f:id:cohalz:20211005104720p:plain
ブロックされたリソースのドメインと発生したブログを集計

読み込みがブロックされたリソースに関して、ドメインごとにグルーピングし(上の図の左側)、それがHTTPSでも配信されているかを1つ1つ見ていくことで、HTTPSへの対応状況を確認しました。

その結果、HTTPSで配信できないリソースはほぼ存在しておらず、新規に読み込めなくなってしまう状況は少ないことが予想できました。

Chrome 86による変更が追い風に

さらに2020年10月に正式版が配信されたChrome 86では、画像に関してupgrade-insecure-requests相当の処理が自動的に入るようになりました。Windows 10でデフォルトブラウザのMicrosoft Edgeも、現在はChromiumベースのため同様の状態になっています。

これにより、とくにupgrade-insecure-requestsを設定しなくとも、既にChromeではHTTPでしか配信されていないリソースが読み込めなくなる状態が発生しており、影響はそれ以外のブラウザ(主にSafariとFirefox)にしかなく、デメリットは限定的であると判断しました。

むしろ、Chromeやその他多くのブラウザでJavaScriptやiframeが読み込めるメリットのほうが大きく、逆に設定したほうがブラウザごとの挙動が統一されることもあり、有効にすることができました。

upgrade-insecure-requestsを有効にした結果

その結果、Mixed Contentの数を10分の1以下まで減らすことができました。

f:id:cohalz:20211004010532p:plain
upgrade-insecure-requestsを有効にした際のMixed Content発生数の変化

TLS接続のパフォーマンスを改善

全ブログをHTTPS化することになると、TLS接続の回数が増え、パフォーマンスへの影響が考えられます。そのため、移行前にTLS接続のパフォーマンス改善を行うことにしました。

上記のように多くの独自ドメインをHTTPSで配信する必要があり、はてなブログではALBのようなマネージドサービスではなく、NginxでTLSを終端しています。このためパフォーマンス改善の余地もある状態でした。

また、2021年5月にはLet’s Encryptの証明書チェーンに変更があり、チェーンが長くなってレイテンシの影響も考えられます。それに備えた対応でもありました。

証明書をRSAからECDSAに変更

最初に、証明書をRSAからECDSA(楕円曲線暗号)に変更しました。ECDSA証明書はサーバ上のパフォーマンスにおいて、RSA証明書より優れています。

一方で、比較的新しいため、接続できなくなる環境があることも考慮する必要があります。はてなブログでは2020年12月にTLS 1.0および1.1の通信を停止している関係上、ECDSA証明書に切り替えたところで閲覧できなくなる環境はほぼ存在しないことが分かっており、問題なく切り替えられると判断しました。

実際の証明書の発行に関して、はてなが提供する*.hatenablog.comのようなドメインでは、上記の「Let’s Encrypt証明書の自動更新システムを作る」という記事で詳しく説明していますが、Certbotを利用した自動更新システムを使用しています。

Certbotは、2020年12月にリリースされたバージョン1.10から、ECDSA証明書を簡単に発行できるようになっています。これを利用することで、切り替えはスムーズに行うことができました。現在、Webブラウザではてなブログに接続して鍵マークをクリックすると、次のような証明書の詳細を確認できます。

f:id:cohalz:20211004000950p:plain
ECDSA証明書に切り替わっている様子

この改善により、下の図で18時前に証明書を入れ替えたのですが、このタイミングでCPU使用率が2割ほど削減できていることがわかります。

f:id:cohalz:20211004005334p:plain
ECDSA証明書に切り替えたタイミングのCPU使用率

暗号スイートを整理

あわせて暗号スイートも整理しました。mozilla wikiの「Security/Server Side TLS」を参考に、TLS 1.2以上をサポートする「Intermediate compatibility (recommended)」の項目を選択しました。

実際の設定は、Mozilla SSL Configuration Generatorから同じ設定を利用することにしました。

セッションチケットでTLS接続を再利用

別の改善として、複数台で行っているTLSの終端において、接続を再利用できるようセッションチケットの設定を見直しました。これまでデフォルト設定のままだったため、セッションチケットは有効だがチケットキーがそれぞれのNginxでバラバラという状態でした。

全台でチケットキーを揃えて、次のようにTLSセッションチケットによるセッションの再開を行えるようにしました。

$ openssl s_client -reconnect -host hatenablog.com -port 443 | grep -e "\(Reuse\|New\)"
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = hatenablog.com
verify return:1
New, TLSv1.2, Cipher is ECDHE-ECDSA-AES256-GCM-SHA384
Reused, TLSv1.2, Cipher is ECDHE-ECDSA-AES256-GCM-SHA384
Reused, TLSv1.2, Cipher is ECDHE-ECDSA-AES256-GCM-SHA384
Reused, TLSv1.2, Cipher is ECDHE-ECDSA-AES256-GCM-SHA384
Reused, TLSv1.2, Cipher is ECDHE-ECDSA-AES256-GCM-SHA384
Reused, TLSv1.2, Cipher is ECDHE-ECDSA-AES256-GCM-SHA384

改善の成果

上記の改善の結果、米Qualys社のSSL Labsが提供するSSL Server Testにおいて、以前はBだったスコアを、次のようにAまで上げることができました。

f:id:cohalz:20211004001224p:plain
Qualys SSL LabsのSSL Server Testの様子

NginxではTLS接続を含めたレイテンシを取得できないため、具体的に表示がどの程度速くなったかは可視化できていませんが、多少は速くなったと感じるのではないかと思います。

Nginxのバージョンアップと予期せぬ改善

TLSの改善にあわせて、Nginxもバージョンアップしました。独自ドメインをHTTPS化したとき(1.13.8)から一度も更新していなかったのですが、施策時の最新(1.21.3)まで上げました。

バージョンアップによって思わぬメリットもありました。以前は、サポートされていないTLSバージョンから接続が来た際に「失敗した」というエラーログに大量に出てノイズになる問題がありましたが、1.15.2からログレベルが変更され、そういったログがデフォルトで出力されなくなりました。

Change: a logging level of the “http request”, “https proxy request”,
“unsupported protocol”, and “version too low” SSL errors has been
lowered from “crit” to “info”.
https://nginx.org/en/CHANGES-1.16 より

これに気づいたとき「古いTLSバージョンを切ることが普通になってきたんだな」と感じました。

独自ドメイン設定時にCAAレコードも検証する

これまで何度か説明したように、独自ドメインのブログをHTTPSで配信するには、そのドメインの証明書を発行できる必要があります。逆に、適切でない独自ドメインではHTTPS配信できません。

何かの理由で証明書が発行できなかった場合、取り得る状態として以下の3つが考えられます。

  • HTTPSではなく、HTTPのブログになる
  • HTTPSだが、有効な証明書が存在しないブログになる
  • 独自ドメインが解除され、はてなが提供するドメインのブログになる

以前は1番目の状態も考えられましたが、全部のブログをHTTPS化した際には、当然この選択肢は取れなくなります。2番目の選択肢は不具合として扱われるケースであり、回避すべきです。

つまり、適切にHTTPS配信を行うには3番目の選択肢を取ることになります。これまでも、ユーザが設定した独自ドメインのCNAMEレコードやAレコードが正しくない場合には、既に3番目の動作になっていました。

しかし、最近利用が増えているCAA(Certification Authority Authorization)レコードについては検証できていませんでした。CAAレコードが適切でないドメインは「新規に設定した際にHTTPSにできない」か「一度発行した証明書が更新できないまま有効期限を迎える」という状態になってしまいます。過去のお問い合わせでも、そういったブログが実際に存在していました。

これはブログの閲覧者にも良くない状態であり、システム上もブログはHTTPSであるという中途半端な状態になります。これを回避するため、独自ドメインの設定時にCAAレコードも検証するようにしました(以下のようにヘルプにも追記しています)。

はてなブログを独自ドメインで利用する – はてなブログ ヘルプ

DNSの設定によっては証明書の再発行を何回もリトライしてしまう状態だったこともあり、ブログ側で事前に検証することで、Let’s Encrypt側のレートリミットに当たらないようにする目的もありました。

キャッシュ削除でHTTPS移行の速度を上げる

ここまでHTTPS移行の実施前に設定したことなどを説明してきましたが、ここで実際にHTTPS移行する際に工夫したことを紹介します。

Varnishキャッシュ削除のジョブがボトルネックに

移行作業の初期には、対象となるブログのHTTPS化が終わったら、そのブログがHTTPのときのキャッシュを削除するため、1つ1つジョブをキューに追加しては実行していました。ここでいうキャッシュとは、リバースプロキシとアプリケーションの間に導入したVarnishです。

しかし、前述のように移行対象のブログは数百万件あるため、そのまま愚直にジョブを追加した場合、キューが詰まってしまう懸念がありました。キャッシュを削除するジョブの実行が遅れたり、それ以外の優先度が低いジョブも遅れてしまったりします。

HTTPからHTTPSへの設定変更自体はすぐ終わるので、キャッシュを破棄するジョブの処理速度に律速され、これがボトルネックになっていました。対策として、もうHTTPのブログはキャッシュを止めてしまうことも考えましたが、もしHTTPのブログに大量のアクセスがあったら、サービス全体の負荷につながってしまいます。

一括で削除し続けるシンプルな手法を採用

そこで、キャッシュをジョブで破棄するのではなく、キャッシュ削除のタグにブログのスキームを事前に追加しておき、HTTPのブログだけを対象に移行作業の裏で数秒ごとに一括でキャッシュを削除し続けるというシンプルな手法を採用しました (Varnishのxkeyモジュールを利用しています)。

これはHTTPのブログがHTTPSのブログに比べて数が少なく、アクセス数も平均して比較的少ないブログが多いことが事前に分かっていたために採用できたことです。

そもそもキャッシュしない選択とあまり変わらないようにも思えますが、キャッシュしない場合は変更をリリースして移行作業を素早く行わないと、大量アクセスに弱いタイミングがどうしても生まれてしまいます(移行作業に不備があってやり直した場合などはなおさらです)。

採用したシンプルな方式では、移行作業をしないときには通常通りキャッシュから返し続けることができ、変更をリリースするタイミングなどによらず自由に作業できることがメリットでした。デメリットとして、移行からキャッシュ破棄まで数秒のタイムラグがありますが、そもそもジョブキューを使った場合も同様のラグはあるので、ほぼ無視できると考えました。

さらに、移行作業が進んでHTTPのブログの数が少なくなるとキャッシュ削除される対象も減っていくので、後半になればなるほど影響も少なくなるというメリットもあります。これにより、実際の移行作業を数時間で終わらせることができました。

こういったキャッシュをうまく考慮した作戦の背景には、2020年に行ったキャッシュ改善の一環で行いやすくなったこともあります。詳細は次の記事を参照してください。

developer.hatenastaff.com

移行するブログ数をBigQueryで把握

最後に、HTTPS配信に関連した変更作業などではなく、移行にあたって対象となるブログの数を素早く把握するため、BigQueryを活用した事例を紹介します。

ここ数年で社内のデータウェアハウスが整備されてきており、はてなブログのデータもBigQueryですぐ集計できるようになっています。この開発者ブログに掲載されている「はてなで働くエンジニアにアンケート」シリーズでも、#14でブログの事例が、はてなブックマークの事例が#16でそれぞれ紹介されています。

ブログによって移行手段が異なることもあるので、独自ドメインのHTTPのブログの数など特定の条件ですぐ集計できるのは、移行手段を考える上でかなり役立ちました。

移行するはてなブックマークの数を把握する

中でも一番活用できたのは、はてなブックマークのデータウェアハウスとの連携です。

HTTPSに移行する際に、はてなブックマーク上のエントリーもHTTPSに移行するという作業が必要になっていました。はてなブックマークとはてなブログのシステムは完全に独立しており、これまでHTTPS化する際にはブックマークされているどうかに関わらず、移行ジョブを投機的に追加するフローになっていました。

しかし、今回の移行対象は数百万ブログあり、エントリ数は数千万といったスケールです。同様のフローで急速に移行してしまうと、はてなブックマーク側で障害が発生する懸念がありました。そこで、本当にブックマークの移行が必要なエントリーがいくつあるかなどを、BigQueryを使ってブックマークされたことのあるブログやエントリーを引くことで、把握しました。

その結果、ブックマーク移行が必要なブログは数千件で、ブックマークされたことのある記事は数万件というオーダーであることがわかり、移行対象としてもBigQueryから出力したリストを使い、問題なく短時間でブックマーク移行も終わらせることができました。

まとめとおまけ

はてなブログがHTTPS化を始めた4年前と比べると、世の中でより多くのサイトが既にHTTPS対応されており、Chromeの画像自動アップグレードを始めとして、HTTPS化への後押しをたくさん感じることができ、思ったよりスムーズに全ブログのHTTPS化を進めることができました。

また、BigQueryの活用やキャッシュをうまく考慮した移行作業など、社内で整備されてきた技術を活用して素早く移行を実施することができ、手持ちの技術をアップデートすることは大事だと実感することが多かったという印象でした。

おまけ1: Let’s Encryptのルート証明書の対応

はてなブログは、社内で最もLet’s Encryptを利用しているサービスで、他のサービスからリクエストを受けるサービスでもあります。このため、2021年9月末をもってLet’s Encryptの古いルート証明書が期限切れになる問題では、影響を大きく受けることが想定されました。

特に古いOpenSSLから接続できなくなるという問題は他人事ではなく、はてなフォトライフを始めとして古くから社内にあるサービスには、まだOpenSSL 1.0.2がインストールされたDebian 8で動いているものもありました。

それがはてなブログやその他サイトへ接続できなくなるサービス継続上のリスクや、構築時にCDNからリソースを取得できない(つまりは新規構築できない)リスクがあることを次のように社内に広く周知し、いくつかは実際にOSのアップデートなども行いました。

f:id:cohalz:20211004003442p:plain
古いOpenSSLへの影響を社内に共有

情報源としては、コミュニティのAPI Announcementsカテゴリや、スケジュールは公式で用意されたACME API Eventsカレンダーを利用しました。

f:id:cohalz:20211008100111p:plain
2020/9/30のカレンダー通知

その結果、周囲の協力もあって、2021年9月30日のDST Root CA X3の有効期限を迎えたタイミングで、影響はほぼありませんでした。

おまけ2: 今年もLet’s Encryptに寄付を実施!

はてなブログでは、ECDSA証明書に移行したり利用範囲を全ブログに拡大したりしたこともあり、Let’s Encryptの利用がより広がっています。そのため、今年も運営団体であるInternet Security Research Group(ISRG)に寄付を実施しました。過去の寄付は下記の通りで、これで4年目になります。

今後もはてなでは、OSSやそれを支援するコミュニティ・団体に対して、今回のような寄付を含むさまざまな形で支援を実施していきます。

はてなでは、技術に対する向上心を持つ仲間を募集しています
エンジニア採用情報 – 株式会社はてな

id:cohalz

はてなブログのSREを担当。2018年入社。2019年8月より現職。

Twitter: @cohalz
GitHub: cohalz
blog: Re:cohalz

はてなリモートインターンシップ2021の講義資料を公開します

CTOのid:motemenです。2021年8月から9月にかけて開催した「はてなリモートインターンシップ2021」も無事に終了しました。

今年のインターンシップは下記のエントリーで発表したように、前半の1週間が講義、後半の2週間は開発を実践する2部構成で、ともにオンラインで実施しました。

はてなリモートインターンシップ2021のカリキュラムを発表します!

このうち講義パートは、Web技術に関するエンジニアリング講義ブートキャンプ、そしてエンジニアリング以外の領域をとりまぜて実施しました。この記事では、それぞれの講義で使用したスライド資料を公開するとともに、内容を簡単に紹介します。

エンジニアリング講義で使用したスライド資料

カリキュラムの中心となるエンジニアリング講義では、昨年実施したリモートインターンシップ2020を踏襲し、サーバーサイド開発に関する講義を行いました。

Kubernetes上で動作しgRPCでサービス間通信を行うマイクロサービスによるブログシステムを題材とし、それぞれの構成要素についてひとつずつ、基礎から学びます。

Web API ─ REST GraphQL gRPC

Webの基本となるHTTPからはじまり、RESTやGraphQLなどのAPI形式についての概説と、ブログシステムで利用するgRPCについて学びます。

はてなリモートインターン2021 Web API 講義資料 – Speaker Deck

Webサービスインフラ入門

Webサービスを実際に動かすためのインフラの概観について学びます。Webアプリケーションをサービスとして動かすために用いられている技術、用いられてきたアプローチを紹介します。

はてなリモートインターン2021 インフラ 講義資料 – Speaker Deck

コンテナ技術とDocker

現代のWeb開発・運用において、Dockerをはじめとするコンテナ技術は不可欠な技術となっていると言ってよいでしょう。コンテナ技術の成り立ちやメリット、Dockerについて学びます。

はてなリモートインターン2021 コンテナ 講義資料 – Speaker Deck

Kubernetesの仕組みとハンズオン

Kubernetesの概念やツールについてハンズオンを交えながら学び、Kubernetesによるコンテナオーケストレーションを体験します。

はてなリモートインターン2021 Kubernetes 講義資料 – Speaker Deck

マイクロサービス

複雑なシステムをスケールさせていくための手段にマイクロサービスがあります。背景からマイクロサービス設計の知識、サービス間通信などについて学びます。

はてなリモートインターン2021 マイクロサービス 講義資料 – Speaker Deck

ブートキャンプで使用したスライド資料

上記の講義とは別に、インターン後半の実践パートにおいて実際のサービス開発に参加するために必要な技術を、単発の「ブートキャンプ」として合わせて講義しました。

現代的なフロントエンド開発 ─ TypeScript React

JavaScript/TypeScriptとReactを用いた現代的なフロントエンド開発についてキャッチアップします。

はてなリモートインターン2021 フロントエンドブートキャンプ 講義資料 – Speaker Deck

RDBMS ─ MySQLの操作とテーブル設計

MySQLを用いた開発に必要なデータ定義やデータ操作、テーブル設計などについて学びます。

はてなリモートインターン2021 RDBMSブートキャンプ 講義資料 – Speaker Deck

Perl再入門 ─ オブジェクト指向やテストまで

受け入れチームによってはサーバーサイドの開発にPerlを利用しています。Perlの基礎から、(Perlによる)オブジェクト指向プログラミングやテストの書き方などについて学びます。

はてなリモートインターン2021 Perlブートキャンプ 講義資料 – Speaker Deck

参考:2020年のインターンシップ資料

一部の講義については、2020年のインターンシップと近い内容になっています。2020年の資料や動画は下記のページ公開していますのであわせてご覧ください。

講義動画と課題 – はてなリモートインターンシップ2020 – 株式会社はてな – 株式会社はてな

その他の領域の講義も実施

今回の公開資料は、上記のようにエンジニアリング講義とブートキャンプのみですが、講義ではそれ以外の領域も取り上げました。ここで内容を簡単に紹介します。

Webエンジニアのためのデザイン入門

エンジニア向けのデザインの基礎と、実際のサービス開発におけるデザイン実例の2部構成で、Webサービス開発におけるデザインについて学びました。

Webサービスの企画 ─ ワークショップを交えて

Webサービス開発における企画について、座学やはてなブログにおける実例の紹介の他に、miroを用いたワークショップを交えて学びました。

AWSによる講義とハンズオン

Amazon Web Services(AWS)から講師を招いて、講義だけでなくハンズオンも行いました。ハンズオンでは、エンジニアリング講義で題材としたブログシステムを対象に、Kubernetes上のシステムをAWSにデプロイし、CI/CDを体験しました。

AWS様、ご協力ありがとうございました。

最後に

今年も非常に充実した講義を準備できたと思います。参加されたインターンの学生の皆さんも真剣に講義や課題に取り組み、後半課程でサービスやシステム開発に参加するための知識を身につけてくれました。

はてなでは来年もサマーインターンを企画していますので、ご興味のある学生さんはぜひ応募をご検討ください。

インターンの情報は、Twitterアカウント@hatenatechでも積極的に発信します。興味のある方はぜひフォローしてください。

そして学生でない皆さん、はてなに入って講義する側になってみませんか? はてなではサマーインターンを通して、たくさんの優秀なエンジニアを送り出してきました。

はてなのサービス開発とともに、インターネットをよりよくしていくため、一緒に働くエンジニアの仲間を募集しています。

はてなでは、技術に対する向上心を持つ仲間を募集しています
エンジニア採用情報 – 株式会社はてな