nazolabo

なぞさんのブログ

退職しました

  • from: UUUM株式会社(2015.06-2019.10)
  • to: まだないしょ

在籍期間中に会社の技術ブログに書いた記事は以下になります。

NESエミュレーターでRustの勉強をした

年明けから特にやることがなかったので、ファミコンのエミューレータを書くのは新しい言語に触れるのにちょうどいいとのことなので、RustでNESエミュレーターを書いてみました。全然動きません。Hello World!が辛うじて動くくらいです。

github.com

Rustってどんな言語?

C/C++の厳しいところを言語レベルで制約をガチガチに加えて安全にした言語、という印象です。最初、GCのあるLL感覚で学習していたのですが、それだと理解が圧倒的に厳しく、「C++のスマートポインタをデフォルトにした言語」と解釈すると圧倒的に理解しやすくなりました。

特に、以下の解説が参考になりました。

qiita.com

https://imoz.jp/note/rust-functions.htmlimoz.jp

Web系の方でも、ObjCでiOS開発しているのであれば比較的近い概念が登場するのでとっつきやすいかと思いますが、RubyPHPのような言語しか書いたことがないという人は概念が違いすぎて大変なのではないかと思います。

最初のうちはコンパイルエラーにとにかく悩まされるのですが、コンパイルエラーにヒントが出ることも多くて解決しやすく、次第にコンパイルエラーになりそうな場所がわかるようになります。一方で、ある程度書いてると「そもそも設計がダメだとビルドにたどり着けない」という現象に遭遇し、場合によっては大幅に書き直しを迫られる場面もありました。設計力も事前にある程度必要なのかと思います。

ただし型制約が厳密と言っても、Vec(配列)の範囲外アクセスなどで簡単に実行時に落ちます。このあたりはパターンマッチなどでそもそも構造的に範囲外アクセスできないようにするなどのテクニックが必要になってくるのかと思いました。

WebをRustで書くというのはあまり想像できませんが、WebAssemblyや組み込み、リアルタイム性が必要な部分などで登場する機会が増えるのではないかと思います。

C/C++を書いていたころを思い出し、書いていてとても楽しいと思いました。普通のWeb系だとGoもあるのでここまで持ち出す必要がなさそうですが、もっと本格的に書いてみたいので、どこかで仕事として書く機会があると楽しいのではないかなと思います。

NESエミュレーターって書きやすいの?

今回初めて書いたのですが、昔より日本語資料が圧倒的に多く、誰でも気軽に参入できる状態だと思います。参入しても何かいいことがあるわけではないですが…。

昔、似たような(もっと高級な)環境の上でプログラムを書いたことがあるので、アーキテクチャの理解は他の人より早いと思います。

冒頭に紹介した記事でもあるように、新しい言語を覚えるのには規模としてちょうどいいレベルではないかと思います。皆さんも是非挑戦してみてください。

ワンポイントTwelve-Factor App(12) : 管理プロセス

この記事は、The Twelve-Factor Appを補足し、実際に現代的なWebアプリケーションで適用する場合の注意点などを紹介するシリーズです。下記の原文を読んだ上でのワンポイント解説になります。

12factor.net

概要

  • 管理プロセスはアプリケーションの実行環境と同一リリースで行う
  • 単発の処理を実行できる仕組みを用意しておく

これは何を表しているか

DBのマイグレーションや単発のスクリプトを実行する場合でも、アプリケーションの実行環境と同じリリースで実行することによって、処理の実行の差を少なくすることができます。

リリースというのは「5. ビルド、リリース、実行」で解説しましたが、コードベースをビルドしたものに環境要因の設定を合成したものです。実行環境と全く同じ構成になっているので、例えば実行環境のWebサーバーを bundle exec … で起動している場合、単発スクリプトbundle exec… で実行することによって全く同じ状態で実行できるようになります。

また、REPLシェルが提供されていると、単発のスクリプトを自由に実行することが簡単になります。本番環境ではSSHなどを通じてリモートからコマンドを叩けるようになっているのが良いでしょう。

実際に運用する場合

Docker環境の場合、単発スクリプトをWebアプリケーションと全く同一のイメージ・同一の環境変数で、 docker run … で起動することによって、確実に同じリリースで実行することができます。コンテナオーケストレーションツールにはこのような単発実行の仕組みが用意されていると思いますので、それらを利用するのが良いでしょう。

本番環境へのSSHに関しては、セキュリティの懸念点や、リリース(「5. ビルド、リリース、実行」)を変更できてしまうという点、autoscaling環境ではそもそも接続先がはっきりしないという点などから、現代では可能な限り行わないほうが良いとされています。本番では実行できる単発処理を制限し、決められた単発処理だけリモートから実行できるような仕組みにしておくのが良いでしょう。

ワンポイントTwelve-Factor App(11) : ログ

この記事は、The Twelve-Factor Appを補足し、実際に現代的なWebアプリケーションで適用する場合の注意点などを紹介するシリーズです。下記の原文を読んだ上でのワンポイント解説になります。

12factor.net

概要

  • ログは全て標準出力に出す
  • (通常は)1行を1イベントとする
  • 標準出力に出したログはログルーターが適切に取り扱う

これは何を表しているか

ほとんどのWebアプリケーションフレームワークは、ログはデフォルトではファイルに出力するようになっています。しかし、「9. 廃棄容易性」といった点から、ファイルに出力してしまった場合は廃棄された場合の取り扱いが困難になってしまいます。

標準出力を唯一のログ出力先にしておくことで、そのアプリケーションがどのように動いているかに関わらず、共通でログを取り扱うことができます。標準出力に出したログはfluentdなどのログルーター(ログコレクター)で扱うことで、アプリケーションとは別にログの取り扱い方を決めることができます。特にDockerでは標準出力に出したログをDockerロギングドライバ経由で柔軟に取り扱うことができます。

実際に運用する場合

Webに限らずアプリケーションでログの取り扱いは最重要です。特に運用段階に入ると、ログのわかりやすさで障害時の対応の速さが大きく変わります。

ログをファイルに出力してしまうと、環境によって取り扱い難易度が変わってしまいます。fluendで取るという理由で一時出力先をファイルにするという場合もなくはないですが、基本的には標準出力に出しておくことでどの環境でも共通で取り扱うことができます。特にDocker環境では標準出力に出しておかないとDocker側のロギングドライバで取り扱うことができないので、必ず標準出力に出すようにしましょう。

設定で標準出力にログを出す仕組みがないログシステムでも、 /dev/stdout に送ることで標準出力に出すことが可能です。

開発環境ではターミナルにログが出ていれば通常は十分だと思いますので、標準出力をそのままターミナルに流しておきましょう。

1行1イベントとなると、CSV/TSVのような形式だとそのログの各項目が何かを判定することができなくなってしまいます。ヘッダ情報を足すにはLTSVでもいいですが、階層構造などを考えるとJSON形式で出しておくことで様々なプラットフォームでの取り扱いが簡単になります。nginxなどのログも含め、基本的にはJSON形式で出力するのが良いでしょう。

本項とは直接関係ありませんが、セキュリティの観点からパスワードなどをログに出さないようにする・問い合わせから追跡しやすいようにユーザーIDやエラーのスタックトーレスをログに出しておく、などの細かい点を抑えておくと良いです。

ワンポイントTwelve-Factor App(10) : 開発/本番一致

この記事は、The Twelve-Factor Appを補足し、実際に現代的なWebアプリケーションで適用する場合の注意点などを紹介するシリーズです。下記の原文を読んだ上でのワンポイント解説になります。

12factor.net

概要

  • 開発環境と本番環境の差異を最小限にする
  • 似たようなバックエンドを吸収するアダプタのようなものは使わない・使ってもバックエンドは開発と本番で同種にする

これは何を表しているか

「手元では動いたが本番で動かない」というのは開発あるあるです。これを防ぐには、開発環境と本番環境がほぼ同一になっているのがベストです。

環境の差異というのは、大きく「コードそのものの差異」と「周辺環境の差異」の2つに分かれます。

「コードそのものの差異」というのは、コードベースが本番と開発で大きく離れてしまうということです。リリースのできない大きな開発が続くとこのような状態になります。小さなコード量を短時間で少しずつリリースし、なるべく本番と開発のコードの量の差を減らすことが重要です。リリースの単位が大きくなると、影響範囲も大きくなり、障害の検知の難易度が上がります。細かくリリースすることで細かなフィードバックを得ることができます。

「周辺環境の差異」は、例えば「キャッシュストレージに開発ではメモリを使うが本番ではRedisを使う」「ファイルストレージに開発ではローカルファイルシステムを使うが本番ではAWS S3を使う」といったことが考えられます。ActiveRecordのようなものを使うと、DBすら「開発ではSQLite、本番ではMySQL」のような構成が可能になります(現実にはかなり厳しいですが)。しかし、これらは似ているようで違うものなので、詳細な挙動の差異により「本番だけ動かない」といったケースが発生する恐れがあります。「似ているから」「アダプタが吸収してくれるから」といって開発と本番で違うバックエンドを使うことは避けたほうがよく、避けることにより「本番で動かない」可能性を最小限にすることができます。

「3. 設定」でも出てきた通り、環境毎に設定ファイルを作ってしまうと環境の差異が大きくなります。開発環境ではデバッグ用ツールなどで多少の差異が生まれることもありますが、極力どの環境もほぼ同じの構成にするようにしましょう。

実際に運用する場合

リリースのサイクルを早めるには、開発手法の見直しが必要です。大きな機能の開発の場合でも、例えば他に影響を与えないコードは先にリリースしてしまう・バックエンドの処理だけ先にリリースしてしまうという方法でリリースの粒度を小さくすることができます。

環境の差異に関しては、開発環境ではDockerを使うことで本番とほぼ同等のバックエンドサービスを用意することができます。S3のようなマネージドサービスでも最近はminioのような互換システムがありますので(金とセキュリティが問題なければ開発時もS3を使うのが一番良いです)、通常の開発時ではdocker-composeで必要なバックエンドサービスを一発でローカルで起動できる状態にすることは容易になったと思います。

開発環境を本番に近づけるにはDocker上(あるいはもっと厳密な仮想環境)で動かすのが良いですが、実際の開発時にアプリケーションがDocker上で動くと処理速度などの点で不利なことが多いので使わないこともあります。その場合でも、例えば非開発者がローカルで動作確認したいという場合を考慮して、 docker-compose up の1コマンドだけで確実に上がる環境を用意しておくと良いです。その環境が用意できると本番でDockerで運用するのも簡単になります。

開発者が全Docker環境を使うかどうかは好みの問題があると思いますが、私は前述の通りDockerでアプリを動かすのは速度面などで不利になることが多いので、「MySQLなどのミドルウェアのみDockerで用意し、PHPRuby、Node.jsといった環境はホスト側で直接起動する」という構成が一番良いと思っており、2バージョンで起動できる環境を用意するようにしています。またこの構成だとgit pre-commit hookなども使いやすいところが良いと思います。

Dockerは仮想化ツールとは違いますので、カーネルのバージョンなどで埋められない差異が多少存在することは頭に入れておきましょう。しかし、一般的な開発でそこが問題になることはあまりありません。

ワンポイントTwelve-Factor App(9) : 廃棄容易性

この記事は、The Twelve-Factor Appを補足し、実際に現代的なWebアプリケーションで適用する場合の注意点などを紹介するシリーズです。下記の原文を読んだ上でのワンポイント解説になります。

12factor.net

概要

  • プロセスはいつでも廃棄されて良いものとする
  • ワーカープロセスなどでは、再入可能な設計にしておく

これは何を表しているか

普通のプロセスに関しては「6. プロセス」や「8. 並行性」にかかれている内容と同様です。並行性が高ければ廃棄容易性も自動的に高いものになります。

いやゆるキューから起動する遅延実行処理のような仕組みがある場合、それらの処理が途中で死んでも再実行できるか、同じ処理が複数回走っても正常な結果になるかといったことに気をつけて設計する’必要があります。

実際に運用する場合

いわゆるautoscalingみたいなことをする場合、現在動作しているアプリケーション環境は突然死んだりします。そのためローカルストレージは突然破棄されたりします。

どうしてもローカルストレージを使わないといけないということもなくはないですが、ファイルは基本的にS3などのオブジェクトストレージに保存するようにし、ログは前述の通り標準出力からDockerのロギングドライバ等を経由し外部ストレージに保存するというようにし、ローカルストレージに依存しない環境にしましょう。NFSなどで永続ストレージをマウントする方法もありますが、取り扱いが難しいので最終手段にしておくべきです。

Webアプリケーションのユーザーからのリクエストで時間のかかる処理を書いてしまうとユーザーを待たせてしまうことになってしまうため、時間のかかる処理はキューに逃がして遅延実行させるということがよくあります。例えば決済処理を遅延処理した際に途中で突然死して再実行した場合に二重決済になるといったことは障害のよくある例です。メールが2回送信されてしまうといったこともよくあります。遅延実行時に限ったことではないですが、重要な処理は再実行が可能になっているかといったことは必ず確認しておきましょう。

ワンポイントTwelve-Factor App(8) : 並行性

この記事は、The Twelve-Factor Appを補足し、実際に現代的なWebアプリケーションで適用する場合の注意点などを紹介するシリーズです。下記の原文を読んだ上でのワンポイント解説になります。

12factor.net

概要

  • プロセスを並べることによってスケールアウトできるようにする
  • プロセス管理ツールによってプロセスを管理する

これは何を表しているか

「6. プロセス」で紹介した設計に即したプロセスであれば、そのままプロセスを違うコンピューター上で複数動作させても問題なく動作するはずです。これにより、プロセスを増やすだけでスケールアウトできるようになります。

また、アプリケーションが自前でデーモン化したりすると、それを共通の仕組みで管理することが困難になります。systemdのようなOSのプロセス管理ツールに任せる・任せられる構成にすることによって、アプリケーションはアプリケーションの動作のみに専念でき、どのような環境でも安全に再起動することが可能になります。

実際に運用する場合

autoscalingのような、自動でコンピューター数が増えるような構成にしたい場合、プロセスの数が不定になり、いつプロセスが増減するかもわからないので、このような構成にしておく必要があります。構成の詳細については他項での説明の通りになります。

通常のWebアプリケーションを通常通りに作成し、ステートレスかつシェアードナッシングな構成になっていれば、そのままプロセスを並べることで並列性を上げることが可能です。とはいえプロセス内の速度は書いたコードに依存しますので、並べられるからといって最適化のようなものが不要になるわけではありません。また、大規模になると「アプリケーションのプロセスは増えるが、それらがアクセスする単一のストレージ(特にDB)が限界になる」という現象が発生します。N+1のようなものには特に注意しましょう。

非Docker環境であれば、開発環境ではForemanのようなツール、本番ではsystemdなどでプロセスを管理することで、アプリケーション側からすると同一の方法でプロセスを管理することができます。Docker環境であれば、開発環境はdocker-compose、本番はECSやk8sといったものを使うことになると思います。

ECSやk8sのようなコンテナオーケストレーションツールでない本番環境の場合、シグナルによってプロセスの再起動が入る場合があります。この際、再起動時にリクエストが来てもエラーにならないように緩やかに再起動する、いわゆるGraceful Restartに対応できているか事前に確認しておきましょう。コンテナオーケストレーションツールの場合でも、リクエストを止める時間とプロセスがタイムアウトする時間を合わせないとユーザーからはエラーレスポンスに見える可能性があります。こちらも必ず確認しておきましょう。