Gunosy Tech Blog

Gunosy Tech Blogは株式会社Gunosyのエンジニアが知見を共有する技術ブログです。

グノシースポーツを支える AWS CloudFormation 活用事例【グノスポ連載第五回】

はじめに

こんにちは。技術戦略室で主にグノシーのインフラを担当している mgi です。
高校時代は水泳部の部長をやっていて、顧問の島田先生に毎日叱られていました。
覚えていらっしゃる方も多いと思いますが、イアン・ソープという超人が全盛期の時代ですね。懐かしいです。

さて、今回はグノスポ連載の五回目ということで、インフラ面についてお話したいと思います。

過去の記事はこちらです。

tech.gunosy.io tech.gunosy.io tech.gunosy.io tech.gunosy.io

背景について

弊社では従来 Codenize.tools をメインで利用し、AWS で構築したインフラのコード化を行っておりました。
シンプルな作りで、良いツールだと思いますが、グノスポ導入にあたり以下のような課題が見えていました。

  • サポートしている AWS Service が少ない
  • AWS サービスを超えた依存関係を記述できない

順に詳しく説明していきたいと思います。

サポートしている AWS Service が少ない

Codenize.tools で現在サポートしているのは、 Security Groups, IAM などを始めとした 12 サービスです。

前回の記事で述べられたとおり、グノスポでは AppSync を始めとして、新しい AWS サービスを導入しようと考えていたのと、Codenize.tools 対象外のサービスもコード化したかった、というのが背景としてあります。

特にコードで管理したかったのは AppSync で使われる GraphQL の Schema です。
なぜ Schema をコードで管理したかったかというと、以下のような理由があげられます。

  • server 側(= AppSync) と client 側で同じ Schema を利用するため
    • 両者同じ Schema 定義を参照しないと、アプリが壊れる原因になってしまうから
  • Schema の変更履歴を追えるようにしたかったため
    • Schema 変更時に GitHub の Pull Request にし、レビューを通すことで、なぜ変更したのか、という理由が残ります
    • 新しい方が join された場合でも、GitHub の Pull Request を見れば理由がわかるので、新規参入者にもよりわかりやすくなると考えてます
  • いざという時に、確実に元に戻せるようにしたかったため
    • GitHub で Pull Request にしている場合、 Revert ボタンで、簡単に確実に戻すことができます
    • この作業が必要なときというのは、もうすでに何かが起こった時なので、手でいじって直すよりは、かなり安心感があると思います

AWS サービスを超えた依存関係を記述できない

例えば Codenize.tools でサポートしている IAM Role は ECS の task など様々な AWS サービスに attach することで効力を発揮するものです。

なのでサポートしているサービスが少ないと、特定の IAM Role がどこで使われているのか、という依存関係を記述できません。
依存関係を記述できないと、以下のような点で少々不便だと感じておりました。

それは以下のような理由からです。

  • 作られた IAM, Security Group だけ見ても、何に紐付いているか、ぱっと見てわかりにくい
    • 今まではある程度整った命名規則をつけることで、この問題に対応してしていたが、時々命名規則に乗っ取りにくいものが出てくる
    • 命名を揃えても、IAM や Security Group をセットするのは手動なので、そこで間違えてしまう可能性がある
  • 依存関係が理由で IAM Role や Security Group の削除時に失敗する。あるいは消し忘れなどが発生しがち

上記の課題に対して、何を選択したか

今回はタイトルの通り、CloudFormation を採用したのですが、採用理由は以下となります

  • AppSync をサポートしている
    • 同様のツールである Terraform は、サポートが完全ではなく、Schema までは管理できない
    • Terraform と CloudFormation のハイブリッド型も考えましたが、同じようなツールを使いこなすのは学習コストもあるので、できれば一つにまとめたいという思いがありました
  • 純正だけあって、サポートしている AWS service の数が豊富
    • 最新のサービスでも大体サポートしているのは流石です
  • 依存関係の記述も書ける
    • DependsOn Hoge のように依存関係を書くことができます
    • !Ref S3Bucket のように書くことで、参照する関係も書けます

現在の運用

基本的には、ほぼ全ての構成を CloudFormation で全て構築、運用しています

https://cdn-ak.f.st-hatena.com/images/fotolife/d/dr_paradi/20181115/20181115014758.png

Stack の分割

Stack は、複数の Stack から参照される「共通 Stack」と、microservice 的な文脈の意味で言う「サービス毎の Stack」で切り分けています。

https://cdn-ak.f.st-hatena.com/images/fotolife/a/azihsoyn/20181126/20181126171339.png

上記の図中、点線部分で囲んである Article Data Storage, Article Preprocessing Component, Crawler という粒度です。

これは AWS CloudFormation のベストプラクティス - AWS CloudFormation にかかれている サービス指向アーキテクチャ に近いと思います。
チーム内のエンジニアに意見を聞き、より直感的だと思った方を採用しました。

実際のディレクトリもそれに近い形で分割しています。

% tree -d -L 1 cloud-formation/
cloud-formation/
├── article-crawler # Crawler に相当
├── article-prep    # Article Preprocessing Component に相当
├── article-search  # Article Data Storage に相当
...

共通 Stack

複数に分割された Stack から共通して参照される Stack も作成しています。
具体的には、ドメイン名だったり、 複数サービスで共通して使われる IAM Role が相当しています。

これらは base という名の Stack を作って管理しています。

% tree -L 1 cloud-formation/
cloud-formation/
├── article-crawler
├── article-prep
├── article-search
├── base.yml   # NEW!
...

Parameters

Parameters - AWS CloudFormation

Parameter とは、CloudFormation テンプレートに渡す引数のようなものです。

グノスポでは、本番、ステージング環境でなるべく差異をなくすため、基本的には同じ構成で構築、運用しています。
とはいえ、ステージング環境で明らかに大きいサイズのインスタンスを立ち上げるのは、コスト的な意味で無駄になってしまうので、そのような差異を Parameters に切り出しています。

具体的に言うと、以下のようなイメージです。

% tree -L 1 cloud-formation/push/
cloud-formation/push/
├── production.json
├── stack.yml
└── staging.json

0 directories, 3 files

Changeset による変更

実は CloudFormation にも、Terraform で言う $ terraform plan のように「これから適用されるであろう変更の確認」ができる機能があります。

それが Changeset です。

https://media.amazonwebservices.com/blog/2016/cfn_the_change_set_1.png

作成した yaml をいきなりドーンと適用して取り返しのつかないことをする前に、
若干面倒ではありますが、一度 Changeset を作って、想定されるであろう変更を管理画面で確認してから、手動で適用という手順を踏んでいます。

良かった点

  • 手動変更を良い意味で検知しない
    • Auto Scaling Group のインスタンス数のように、若干コードで変更して deploy する、よりも手で変更したほうが早いケースがいくつかあります
    • そういうときに、気軽に(?) 変更でき、且つ、他の人の作業に影響でないのが嬉しいです
    • 最近、この差分をチェックできる機能がリリースされました。オプショナルで確認できる機能は待ち望んでいたので、非常に嬉しいです
  • 自動で Rollback される
    • 失敗したときに、(限界はありますが)自動で Rollback してくれるため、思い切って差分を適用できるのが嬉しい

悪かった点

  • 後から Stack のリファクタリングができない
    • 一度 Stack で管理してしまったリソースを、別の Stack に移動ない
      • 削除 + 作成になってしまう
      • 諸事情により、これができないものが一部存在する
  • import できない
    • 既存の設定を後から CloudFormation に乗せられないのは不便で、大した内容ではないのに yaml と格闘する時間が長くなってしまったこともありました
    • そういうときに手でさっと作って、それを CloudFormation 管理にできたら非常に助かるなと思いました
  • yaml の肥大化
    • 最初はそこまで大きくなる予想はしてなかったですが、 中には 1900 行まで育った、最強の yaml が存在しています
    • 後からリファクタリングできないので、詰んでいます
  • 共通 Stack の設計ミス
    • 「最初は環境に依存しないリソースを管理する Stack」というルールだったが、それが途中で崩れてしまった
    • 後からリファクタリングができないので、そのまま運用を続けるしかなくなってしまった

対策

まだ着手はできていませんが、上記の悪い点を対策するために、以下のような対策を考えています。

  • 全てを管理するのではなく、一部だけ CloudFormation で管理する
    • 各リソースに DeletionPolicy をつけてから、yaml から記述を消していく、みたいな流れで、CloudFormation の管轄から外れます
  • Terraform を採用する
    • Stack のリファクタリングが困難、という部分は CloudFormation の性質上どうしようもないので、別のツールで管理してしまう、という方針です
    • しかし前述の通り、AppSync の Schema 部分だけは、CloudFormation で管理し続け、Terraform 側でサポートされたら、全てを乗り換えるつもりです
  • aws-cdk を使ってみる
    • aws-cdk とは、TypeScript で CloudFormation のテンプレートを書けるというツールです
    • 軽く触ってみた限りでは、非常に好印象でした
    • github.com

おわりに

グノスポでの CloudFormation 活用事例についてお話しました。
まとめると以下のとおりです

  • グノスポでは CloudFormation 使って構成管理しています
  • CloudFormation で特に難しいところは Stack の粒度、管理対象の範囲で、現在の仕様では後から変更することが難しいところです
  • 逆に言うと、 1 Stack が十分小さく、 yaml の行数が少ない状況で導入できると、かなりいい感じになりそうです

CloudFormation 運用前に事例を結構探したつもりなのですが、あまり出回ってないようでしたので、この記事をきっかけに各社色々出してくれると非常に嬉しいです。

最後になりますが、グノシーは技術事例だけではなく、一緒にプロダクトをより良くしてくれる仲間も募集しています! 興味のある方はご応募いただけたら嬉しいです!

https://hrmos.co/pages/1009778707507720193/jobs/0000007hrmos.co