猛烈に成長するSaaSのインフラを猛烈にカイゼンする技術

SREチーム 鈴木心之介 です。 職歴の空白 を経て参画しました。 社名変更して co.jp ドメインを複数保有する技術 の節は皆様ありがとうございました。

たぶんそのうち書かれるだろう「Dockerコンテナ移行しました」記事の先史時代の記録として、また、事業の成長に併走してきたEC2でのアーキテクチャの御焚上として奏上するものです。

問題意識

アプリケーションはRuby on Railsで実装し、インフラはAWSにEC2, RDS, S3を中核に構成してます。運用状況はEC2に限らず大変きびしく、早くどうにかしないと事業の成長の足枷になりそうでした。入社前のカジュアル面談で伺っていた情報と、入社後の情報収集から、大枠の問題意識を以下4つに絞りました。

  • デプロイメント
  • セキュリティ
  • スケーラビリティ
  • ディザスタリカバリ

どれも解決すべきで、優先順位にみなさま一家言あるかと思います。ただセキュリティとディザスタリカバリに取り組もうにも、まずはデプロイメントとスケーラビリティを整備しないことには話が進まないレベルでした。

そこで、このあたり実現できるよう、その時々の現状から導入可能な要素技術を刺してきました。

  • プロダクト開発を牽引するメンバーを、リリース作業から抜けられるようにしたい
  • ちゃんとAutoScalingすることで、事業の成長に勝手にインフラが追いかけるようにしたい
  • 誰でもリリース作業実施できるよう整備し、私がリリース作業から抜けられるようにしたい
  • 現インフラ要素に固着していた諸々を切り離し、異なるインフラ技術に乗せかえできるよう整備したい

リリース手順をExcel手順書に整備した

Excel手順書かよ...と思いましたね?

私が参画した当時はエンジニアの人数も少なく、リリース作業を実施してるのはCTO金近(兄)テックリード金近(弟)、他数名のみ。リリース手順は頭の中にだけあり、毎週火曜22時頃にオフィスのミーティングスペースに集って25時くらいまでやってました。翌朝になると、xxxxをyyyyするとzzzzなるのですが何ですかこれは云々などと問合せがあり、調査、データパッチ、バグフィックスリリース。

こんなタイムテーブルで仕事すれば直ちにわかりますが何も仕事になりません。リリース作業してるメンバーは、同時に並外れた量のプロダクト開発のプルリクを出しているメンバーでもあります。リリース作業ごときで疲弊させるわけにはいきません。

巻き取りにかかりました。手作業してるので、なんぞ手順メモでもあるのかと思いきや、頭の中にしかない。ないものをなじっても話が進まないので、Excel手順書にしていきます。立ち合って、やってるところを実演していただいて、メモっていき、次回は私にやらせてもらい、過不足や条件分岐をExcel手順書に追記していきます。そんなで何度かリリースやると、頭の中からExcel手順書にアウトプットできました。文書化できると、文書をもとに改善していけます。

モニタリングをDatadogとCloudWatchに集約した

インフラ監視はMackerelを、APMにDatadogを、Mackerelで課金されるインフラ要素はCloudWatchを、と組まれていました。監視と通知は狼少年で、ダッシュボードは荒廃し誰も見ていない、開発部門以外から「ちょっと、なんか重くねえっすか」と相談されて気付くなど、凄惨な状況でした。

私が前職でDatadogに慣れてた縁もあり、インフラ監視をDatadogに寄せて、ダッシュボードをDatadogで整備しました。

監視と通知は、「鳴ったら誰が何やるの?」で無意味なの削って、「ユーザーや他部門がザワつくより早く対処を開始するには何を監視したらいいの?」で不足した観点を整備しました。通知より先に、ユーザーや他部門から連絡を受けた障害には、いちばん最初に反応するメトリクスを探し、アラート仕込みました。障害が起きる度に繰り返していくと、安心できる監視に育ちます。

通知にはイの一番に反応し、状況分析と対処し、誤検知が続けば調整し、というのを継続して、監視と通知を信じられるものに育てていきました。

唐突ですが入門監視は良書です。

www.oreilly.co.jp

PackerとAnsibleでゴールデンイメージを作った

ウェブアプリのソースコードは、GitHubでmasterマージしたら、リリース用サーバーから、capistranoで各EC2にリリースしていました。

インフラ部分の変更は、このように運用していました。

  • AutoScaling起動設定にバンドルしてるAMIの、元ネタにしてるEC2インスタンスを起動して、
  • SSHして、
  • もろもろ変更を加えて、
  • AMI作成して、
  • AutoScaling起動設定を作成して、
  • AutoScaling GroupのAutoScaling起動設定を差し替えていました。

カジュアル面談と面接で、めっちゃ苦境を聞いており驚きはありませんでしが、いざ現物に触れると、なかなか感慨深い。

Packer で Ansible を動かせば、Git管理で再現性でレビュープロセスが、と良いことづくめなので、そのように整備しました。

qiita.com

負荷に応じたAutoScaling にした

AMIでAutoScaling Groupで組んでありました。しかしAMIには最新のmasterが乗っておらず、リリース用サーバーから、capistranoしたときに乗る仕組みでした。

スケールインは大丈夫です。

スケールアウトしたときは古いのが起動してしまいます。対策はされていて、EC2インスタンス起動時に、git pull, bundle install, yarn install, assets:precompile など諸々を済ませてました。30分かけて。エッ。30分かかってたら「クラスタの負荷に応じて増減」は無理です。そのとおりなので、スケジュールアクションで、朝のピークの1時間くらい前に固定台数にスケールアウトしておき、夜はコスト削減のためスケールインしてました。これは苦しくて、不定期に固定台数の見直しが必要になります。なのに判断水準も用意がありませんでした。どうにかするぞ。

Packer で Ansible でゴールデンイメージを作るときに、git pullからの一連を済ませておき、スケールアウト時はnginxとpumaが起動するだけにしました。字義どおりの「ゴールデンイメージ」ですね。以前は30分かかっていたのが、約7分でALB Target GroupでHealthyにできました。まだ遅いですが、AutoScalingのスケールアウトには耐える時間です。

あとはAutoScaglingでCloudWatchメトリクスをトリガーにスケールアウト、スケールインさせるだけです。が、スケールアウトさせる閾値を高くしすぎて、ピーク時のスケールアウトが間にあわずサービス停止したり、AutoScaling Groupの最低台数を低くしすぎてゼロ台になる瞬間ができてしまったり、何度か本番障害になり、今となっては、、、、今となっても苦々しい思い出です。今はすっかり安定しています。

ウェブサーバーを Blue Green Deploy にした

毎週火曜22時頃にオフィスのミーティングスペースに集って実施してました。他に、ホットなバグ修正リリースを日中にも実施してました。手順書作成や、リリースプロセス改善のため立ち合ってましたが、リリースの翌日はキツいです。

いや営業時間にリリースしましょうよ、何がダメなんですか。ヒアリングしていくと、下図のような組織的な構造が積み上がって夜リリースになっていました。下端「終電」から、上端「リリースつらい」に戻る、ひどい悪循環です。「日中にやろう」という組織内合意だけでは解決できそうにありません。

f:id:sasasin_net:20201209193042p:plain

よく眺めて考えてみたら、 Blue Green Deploy がフィットする問題です。ALBを使っていたのはラッキーでした。オフィスには固定IPがあり、ALBリスナールールで「アクセス元IPアドレスが、社オフィス固定IPだったら」でトラフィックを流すALB Target Groupを違えることができたので、

  • ALB Target Group を Blue と Green の2個用意する
  • AutoScaling Group を Blue と Green の2個用意する
  • ASG Blue のインスタンスのアタッチ先はALB TG のBlue。ASG Green のインスタンスのアタッチ先はALB TG のGreen。とした
  • リリース時は、インターネットに公開してない側に、新たにPackerで作ったAMIをくっつけてテスト
  • テストOKなら、インターネットに公開。ダメなら追加のプルリクを入れたAMIをくっつけてテスト

として Blue Green Deploy を実装できました。数回はリリースオペレーションを確立させるため夜間リリースし、以降は日中営業時間にリリースしています。日中営業時間なので、プルリクの作者やプロダクトオーナーが本番動作確認できます。Blue Green Deploy で社オフィスだけに公開、だめだったら切り戻せるので、じっくり動作確認できます。などにより、悪循環を断てました。

リリース手順の一部をCodeBuildに固めた

Blue Green Deploy を実現しましたが、実現できることを最優先にしてました。Excel手順書には、AWSマネコンをカチカチやる手順を子細に記載していましたが、手数が凄まじく、誰でも何度でも確実に遂行可能かというと、ぶっちゃけ私しかできない状態になってました。

AWSマネコンをカチカチやる。AWS CLIでやれ事案です。Packer と Ansible でAMIを作るのは CodeBuild でやってた縁もあり、本件も CodeBuild で AWS CLI をシェル芸して、AutoScaling Group, ALB Target Group, ALB リスナールールをいじくりまわす闇の深い複数個のCodeBuildジョブが爆誕しました。Excel手順書の5手くらい毎で「CodeBuildジョブ hogehoge を start build し、正常終了を待つ」に置き換わっていきいました。

そのくらい単純になると、私以外の方にもお願いできるようになります。今度は私が実演し、私以外の方に展開していきました。

ステージング環境を整備した

ステージング環境がありませんでした。まったく影も形もありませんでした。上記のとおり既に改善施策を積んできましたが、全部ぶっつけ本番です。まったく恐ろしい。無いことをブー垂れても始まらない。整備しました。

ステージング環境といえば、前職でも現職でも整備するハメになり、前職の比でない大変さに心が折れそうになりましたが、以下記事を励みに、どうにか進めました。

ohbarye.hatenablog.jp

AMIは、PackerとAnsibleでAMIを作る仕組みを整備していたので、元ネタのAMIを攫って、ステージング環境作りに供することにしました。

データベースは、以下のように仕組みを整備してRDSスナップショットを作り、ステージング環境作りに供することにしました。

qiita.com

AMIとデータベース以外の仕組みは、、、TerraformとかCloudFormationとか。そんなものは無かった。本番環境を眺めながら、せっせとステージング環境用のVPCを作り、ElastiCacheをつくり、S3を作り、、、、アプリケーションコードの設定ファイルを作って、、、と、ちんたらやって、なんか、どういうわけか作れました。社内みなさま協力ありがとうございました。

Ansibleでセキュリティパッチ当てた

いやまさか。本番でいきなり sudo yum update -y なんて。ソンナ、アリエナイッス。

ステージング環境ができあがったことで、ステージング環境でセキュリティパッチを当てて動作検証してから、本番に出せるようになりました。かなり溜めてしまっており、とはいえ先送りもできないので、オラーーッと溜まってるの全部当てるリリースやりました。

PackerとAnsibleでゴールデンイメージを作るところに、こんな雰囲気のAnsible taskを追加して、当てるようにしてます。

---
- name: security update for amazon linux
  shell: yum update -y --advisory="{{ item }}"
  register: result
  changed_when: '"No packages needed for security" not in result.stdout'
  with_items:
    - ALAS-2020-1375
    - ALAS-2020-1381
    - ALAS-2020-1392

セキュリティパッチは https://alas.aws.amazon.com/ のRSSを眺めてます。

リリース手順の大半をCodePipelineに固めた

Excel手順書には、「CodeBuildをstart buildする」を多数列挙してあり、一部は他と並列させてもよいのですが、言語表現の難しさの壁から、無駄な待ち時間がかかってました。

AWSには、CodePipelineという便利なものがあります。JenkinsとかCircleCIでワークフローとか呼んでるやつですね。

CodePipelineは、CodeBuildジョブを順番に実行したり、並列で実行させて全部正常終了したら先に進めたり、といったことを組めます。都合よいことに、承認プロセスという部品もあり、「ここでAWSマネコンでApproveボタン押すまで待つ」を実現できます。これでExcel手順書をだいぶ簡略化できました。

ここまで簡単になると、「Excel手順書のとおり、アナウンスして、CodePipelineをカチカチすればいいです」まで持っていけます。アプリケーション開発チームで当番制を組み、リリース作業が、ついに私の手を離れました。

秘匿情報をSecretsManagerに置いた

PackerとAnsibleで作るゴールデンイメージの、入力にしてるAMIがあります。

アプリケーションが利用する秘匿情報(DB接続パスワード、外部SaaSのAPIトークン、などなど)を、このAMI内の実ファイルとして転がして、かつ、アプリケーションのGitリポジトリではgitignoreしています。まさか秘匿情報ごとgit管理するわけにもいかず、ああー、いつか考えよう、で放置してました。放置して、ある夜に閃きました。

  • AWS の SecretsManager に、秘匿情報を置こう。1個のシークレットに65536バイトまでのJSONで key value をぶっこんでおける
  • PackerとAnsibleでゴールデンイメージを作るCodeBuildジョブで、
    • SecretsManagerから取ってきたJSONを加工して、環境変数宣言の形式( export KEY="value" )に整形して、AMIの中に /etc/profile.d/hogehoge.sh みたく置く
    • ウェブアプリは /etc/profile.d/hogehoge.sh から環境変数を拾えるよう起動させる
    • ウェブアプリの設定ファイル(Ruby on Railsなので config/settings/production.yml とかですね)の値は、環境変数から取るようにする。典型的にはこのように
aws:
  aws_access_key_id:        "<%= ENV['AWS_AWS_ACCESS_KEY_ID'] %>"
  aws_secret_access_key:    "<%= ENV['AWS_AWS_SECRET_ACCESS_KEY'] %>"

とすることで、Git管理と、秘匿情報を秘匿しておくことができました。Twelve-Factor App には環境変数にしろとあるけど、AWSではどーするとか、ググっても出てこないのでまじ厳しかった。

デプロイメントとスケーラビリティに絞って、改善施策やってきました話を書いてみました。

各方面からお知恵を拝借したり協力を得たりで、インフラが追いつかなくて事業が止まる事態は回避しつつ、走れました。この程度には改善を進めた技術スタックと向き合ってます。たぶん半年後くらいには、Dockerコンテナ移行しました記事が出るんじゃないでしょうか。

長文お読みいただきありがとうございました。

ところでアンドパッドは仲間を募ってます。SREも仲間を募ってます。どのポジションも仲間を募ってます。わりとキッツイです助けてくださいお願いいたします。

engineer.andpad.co.jp