一定の制約を設けてRailsのサービスクラスに一定の秩序を設ける

Railsでのサービスクラス(またはオブジェクト)の是非はさておき、Railsでサービスクラスは導入するとルールがゆるふわになりがちだったりします。

例えば、「サービスクラスを実行するメソッドはクラスメソッドなのか、メソッド名は execute なのか、perform なのか、 call なのか… どれがいいとか悪いとかはないですが、これらに対して一定の制約を設けることで、サービスクラスに対して一定の秩序を与えていきましょう。

今回設定するサービスクラスへの制約について

  1. 必ずベースとなるmoduleをincludeすること。
  2. クラス名には必ず「Service」を含める。(含めないとエラーになるようにしています。)
  3. initialize は必ず定義して、インスタンス化させる。
  4. 必ず、クラスメソッドからperform サービスクラスを実行する。

まず、サービスクラスのベースになるServiceBaseというモジュールを実装します。

module ServiceBase
    class ServiceClassRuleError < StandardError; end

    def self.included(base)
        base.extend(ClassMethods)
        raise ServiceClassRuleError.new("#{base.name}: Please Rename Service Class") unless base.name.include?("Service")
    end

    module ClassMethods
        def perform(*args)
            self.send(:new, *args).send(:perform)
        end
    end

    private

    def initialize
        raise NotImplementedError.new("You must implement #{self.class}##{__method__}")
    end

    def perform
        raise NotImplementedError.new("You must implement #{self.class}##{__method__}")
    end
  end

このベースになるモジュールの役割としては、

  1. サービスのクラス名に Service という名称がクラス名に含まれているかをチェックします

  2. initalize メソッドを必ず定義させるために、未実装の場合にエラーになるようにしています。

  3. perform インスタンスメソッドを必ず定義させる。このインスタンスメソッドはperform クラスメソッドからのみCallされるようにするために、気休め程度ですが、プライベートメソッドにしておきます。

  4. クラスを内部的に new して、インスタンス化して、perform インスタンスメソッドをCallするための、 perform クラスメソッドを定義します。

というのが大きな役割です。

実際にServiceBase moduleを使ってサービスクラスを実装してみましょう。

class PostCreateService
  include ServiceBase

  private

  attr_reader :title, :content

  def initialize(title:, content:)
    @title = title
    @content = content
  end

  def perform
    Post.new(title: title, content: content).tap do |p|
      p.save
    end
  end
end

次はControllerでサービスクラスをCallしてみましょう。(今回の例ではちょっとサービスクラスの旨味がないですが…)

# ...

def create
  @post = PostCreateService.perform(title: post_params[:title], content: post_params[:content])
  respond_to do |format|
    if @post.valid?
      format.html { redirect_to @post, notice: 'Post was successfully created.' }
      format.json { render :show, status: :created, location: @post }
    else
      format.html { render :new }
      format.json { render json: @post.errors, status: :unprocessable_entity }
    end
  end
end

# ...

暗黙的にクラスメソッドの perform が実装されていて、意図しないクラス名が設定された場合でもエラーになるようになっています。 これでサービスクラスの実装する人も迷わなくて済みそうです。

応用例

今回、サービスクラスに対して一定の制約を設けるためにベースになるModuleを追加しましたが、 このModuleを応用してRailsRunnerだったり、Rakeタスクで処理を行うためのクラスを実装する際にも制約を設けて役に立たたせることが可能です。

パッケージマネージャからインストールしたLet's Encrypt(Certbot)で証明書の更新をCronで実行する必要はない

まとめ

  • Linuxディストリビューションのパッケージマネージャー(aptとかyumとか)からインストールするとすでに更新処理(certbot renew)はCronで自動化されているため、自前で設定する必要はありません。
  • ただし、HTTPサーバによってはreloadかrestartをしないと証明書の内容が更新されないので、それは自前で行う必要がある。

もうちょっとだけ詳しく

  • ディストリビューションでどのバージョンから自動更新処理はがついていることが下記URLにも記載されている。こちらを参照しよう。

certbot.eff.org

  • どこで実行されているかわからんときや、自分の環境では自動化されているかどうかを確認したい場合は、/etc/crontab だったり、 systemctl list-timers でチェックしてみましょう。

実際に利用しているUbuntu18.04では、下記内容がCronにしれっと登録されていた。

# /etc/cron.d/certbot: crontab entries for the certbot package
#
# Upstream recommends attempting renewal twice a day
#
# Eventually, this will be an opportunity to validate certificates
# haven't been revoked, etc.  Renewal will only occur if expiration
# is within 30 days.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew
  • このCronを読んで貰えればわかるけど、証明書の更新はしてくれるが証明書を利用しているHTTPサーバのReloadやRestartはされないので、証明書の再読み込みに必要であればそれは自前で行う必要がある。

思うところ

  • 最近からそうなったんだろなーとは思ったけど、これで理由が理解できた。
  • 殆どの利用ケースではパッケージマネージャ経由でCertbotはインストールしているだろうし、よくわからない人はパッケージマネージャ経由で入れておけという感じです。
  • 思考停止状態でとりあえずでコピペ設定するのはやめよう。設定するならHTTPサーバのReloadだけで十分だし、都合が悪いので自前で叩く場合は、既存のCron設定は止めよう。

なるほどUnixプロセス - Rubyで学ぶUnixの基礎

元々興味があった本で、GW中に読むかーというので、結構短めの本だったのだけど、 ゲームをしながら(Borderlands)読んでいたので、3日位かかった。(なので実質3〜4時間くらいかなぁ。)

tatsu-zine.com

目次では20章あるけどほとんどの章が2−3ページなので、長ったらしい前置きもなくてとても読みやすいし、 写経するコードも基本めちゃくちゃ短いのでホントにサクサク読み進めながら試すことができる。

ファイルディスクリプタ、ゾンビプロセス、コピーオンライト、Preforkなどなど、Unixやプロセスを扱う上で基本的な知識や、 UnicornとResqueがプロセスの観点でどう動いているかの簡単な解説も載っていて、本のタイトル通り、なるほどとなるような内容でした。 とりあえずこれ読んでおくだけでもUnixのプロセスについてよくわかっていない状態から、少しは分かるようになるところまではステップアップできると思っています。

細かい話でいうと、Railsのデプロイタスクでよく使う、 UnicornのGraceful Restart(そもそもなんのシグナルで再起動になるかわかってますか??そもそもシグナルって??)で、新しく追加・更新した環境変数が反映されないーみたいなトラブルとか仕様みたいなのがあるけど、 これはこの本を読了した際にどうしてなのかをちゃんと説明することができるようになっていると思う。 (なんか知らんけど Capistranostop -> start したらなんか反映されたみたいなのはダサいですよ。

正直、このボリューム(ページ数という意味)で書籍としては正直少し割高感があると思うんですが、 内容がそれに見合っていると思うのと、ページ数が少ないのは簡潔に説明できている証明であると思うので、買うのを躊躇する理由が金額なら躊躇しないで絶対に買ったほうがいいと思います。

iOSアプリ開発デザインパターン入門を読んだ

会社で電子書籍をシュッと買ってもらえたので、ちょくちょく読んでいたのですが、 GW中に残りを読了したので感想をササッとまとめて書いておきました。

  • デザインパターンというと、この本の中ではオブジェクト指向プロトコル指向・MVC・MVVMについて説明しているのでたしかにそれらはデザインパターンかもしれないけど、一般認知されているデザインパターンってJavaGoFデザインパターン本をイメージする人は多いと思うので、ちゃんと目次を見て買いましょうという感じでした。僕はわかってて買ったのでとくに問題がないのでした。

    • デザインパターンではなく、iOS初心者が中級者になるための手引き的な本…? というのが個人の感想です。(元々別タイトルで出してたけど、商業出版する際にタイトルが変わったのだろうか
  • 肝心の内容ですが、前述の通り、オブジェクト指向についてのおさらい、プロトコル指向について、Viewや非Storyboard利用時のなどのTipsを中心に割いている章、iOSMVCのサンプルコードを実践に近い感じで実装する章。MVVMのサンプルコードを「外部ライブラリ依存なし」書いていく。という感じでした。

    • MVCの章が一番良かった。
      • 初心者から中級者の過渡期の中で不安になるのが、「とりあえずMVCで書けると思うけど、行数も多いし、これで正解なんだろうか?」だと思っていて、この章のコードはその点において、不安点を解消する良いお手本、正解コードのひとつと思った。
    • 次のMVVMもとてもわかり易かった。
      • 「ReactiveXX」とか「RxSwift」が「ほぼ必須」な感じがする昨今、これらのライブラリをなしにして説明をしているのが大変良かった。
      • MVVMよりもReactiveXXとかRxの概念説明のほうがとにかく大変そう。
  • 不満点をあげるとするなら、この本だけではないけど、MVCとかMVVMを説明している本でよくあることだとは思うのだけど、MVCとMVVMで同じケースを実装して比較してほしいところがそうではなかった(MVCはタスク管理アプリでMVVMはGitHubクライアントアプリ)ので、単純な比較にならないんじゃないかと思ってて、そこはちょっと不思議に感じている。

    • 両方同じユースケースで実装して、MVCだとこの辺辛いので、MVVMにしようね。とかそういった切り口がないなぁと。
    • DDDとか設計論全般的にそうだけど、少なくとも自分は、成功しているコードや設計だけを見せられても正直なところ実感がないというのがあります。アンチパターン集みたいなのがわかりやすくて好きです。
  • iOS初心者 -> 中級者入門くらいへ向けての本って全然ないと思っていて(Swiftならあるんですよ。)、ちょうどいい感じにこの本が刺さったので大変良かった。似たようなターゲットの本だったり、もっと上級者というか深くダイブしたい人向けの本が技術書典をきっかけにどんどん生まれてくればいいなぁという感じでした。

【WIP】TLS終端サーバーを冗長化するために考えること

TLS終端サーバーのロードバランシングについて

すいません。完全に知識不足でDNSラウンドロビン以外で、冗長化できないとおもっていました。 普通にL4ロードバランサで冗長化できましたね。はい。

説明する前にロードバランサについておさらい

解決策

  • DNSにはL4ロードバランサのIPを指定する。
  • L4ロードバランサからTLS終端サーバー群へロードバランシングしてくれる。

はい、これだけですね。 よくよく考えれば当たり前のことだと思いました。

もちろん当たり前なのですが、SSL証明書TLS終端サーバー群に対してインストールしておくことが必須になります。

完全に勘違いしていたこと

  • DNSに設定するのはTLS終端サーバーのものを直接指定しなければ行けないと思いこんでた。
    • L7がTCP終端しているのをそもそも理解していなかったことと、L4がTCP終端しないでそのままパケットを流すことを理解していなかった。
      • DNSで設定したロードバランサ上でもうSSL証明書を解決できないと行けないものだとばかり思っていた。
      • L4がそのまま処理を流すのであればDNSが知っているべき宛先としてはL4のロードバランサで問題ない。
      • TLS終端を実際に行う部分でSSL証明書を解決できれば実はOKなのだ。(ここでTCP終端しているわけだから。)

冗長化したTLS終端サーバーの証明書関連の課題

  • とにかく難しいと思っている。(ここから本題感ある。)
    • というか難しいからハードへのリスク承知でSPOFにした記憶
      • そしたら運良く(悪く?)障害を引いた…
      • そもそもクラウドの場合、冗長化しないとSLA保証されない場合があるしねぇ…
  • TLS終端サーバーがすべてのアクセスの起点になっているので、TLS終端サーバーで粗相があるとすべてのアクセスに対して問題が発生してしまうから難しい。

証明書の読み込み

冗長化するし、設定したSSL証明書をどうやってサーバー間で共有するかも課題。 アプリケーションサーバーなんかよりも考えることも多くて大変。

これはいくつか対応策はあるけど、できればインフラ専任エンジニアがいないので、なるべく普遍的な技術スタックでやりたい。。。

シンプルに証明書部分だけをNFSでマウント

  • 必ずこの部分がボトルネックになるので却下。
  • そもそも弊社でSPOFだったTLS終端サーバーに対して、冗長化を少し考え始めたのは、ディスク(インスタンスに対してという意味)破損があったからなので…

ngx_mrubyで証明書を動的読み込み

TLS終端サーバーを自前運用しない

  • AWSだとALB、AzureだとApplication Gateway使えってやつですね。
  • 本当に特別な事情がない限りこれが安心安全。
    • AWSとかAzure自体が駄目だったら、言い方は悪いけど、クラウドベンダーのせいにできる。
    • それで怒られるような規模なら専任にインフラエンジニアいるでしょ。
  • ただし弊社案件では、独自ドメインの証明書を使いたいのと、設定する証明書に上限は設けられないので使えない。
    • 使えるなら絶対に使っている。
    • ALBやApplication Gatewayは1つにつき証明書が20〜25だったと思う。
      • 足らんわっ・・・ まるで・・・

Let’s Encryptでの証明書の取得・更新について

弊社では独自ドメインの証明書をLet’s Encryptを使って取得しているのでそっちの都合で「証明書の取得や更新」について色々考えてみました。

これもさっきのはてなとペパボの記事でLet’s Encryptでの証明書を取得する事例について紹介されているので、 それを見て自分でどう思ったかを簡単にまとめておく。

  • speakerdeck.com
  • speakerdeck.com
  • tech.pepabo.com

  • 独自ドメインSSL証明書Let’s Encryptでやる場合、TLS終端サーバーとは疎結合な仕組みにしておくと幸せになれると思った。

    • 例えば、TLS終端サーバー「にだけ」SSL証明書を保存しないようにする。

      • RDBにも保存しておくといいし、念のためにS3にも入れておきましょうなど。
        • しかしこれを実現するためには「SSL証明書取得システム」が必要になる。というかないと厳しい。
        • SSL証明書取得システム」はお手軽にシュッと作れるものではないと思ってて、実装コストもまぁまぁ高いと思う。
        • 弊社用途だと、レンタルサーバー屋さんでも大手ブログサービスでもないのでそこまで爆発的に増えることはしばらくないと思うんだけども…
    • TLS終端サーバーの責務は「証明書を名前解決して後続に処理をルーティングさせる」ことだけに集中させるべき。

      • 責務が多いと、ngx_mrubyの処理が多くなって来るし、証明書を取得するのは初回だけ、更新も3ヶ月に1回なので。
      • 更にいうと、サイトによってはEV証明書使いたいので、Let’sEncryptを使いたくないケースも想定されるので、あんまりべったりしているとあとから大変になる。
      • 更新方法を素朴に「TLS終端サーバーでCronを回す」のはお手軽だけど更新漏れに気づけない場合があるし、気づくための実装がとにかく大変。
        • なので上記のようにサービス分割しておくと、実装も楽になるともう。

まとめ

  • TLS終端サーバーの冗長化、とにかく大変そう…😇
  • 弱小スタートアップがサクッと作れるようなスタックがほしい。。。