Gunosy Tech Blog

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

Infrastructure as Codeの心構え

この記事は Gunosy Advent Calendar 2018 2日目の記事です。

こんにちは。技術戦略室 SREチームの @aibou です。昨日編集中の記事が吹っ飛んだので1日遅れのアドベントカレンダーです。 さっそく遅延して本当にすみません。。。

みんなきちんと自動保存されるようなエディタで編集しような!

この記事では、僕が日頃どんなことに気をつけてInfrastructure as Codeを実践しているかの心構えについて記載します。 (今回のInfrastructure as Codeはサーバのプロビジョニングの話ではなく、AWSやGCPの構成管理の意味合いで捉えていただければと思います。)

選定

前回?のアドベントカレンダー(2015年)では、導入したツールの話をしました。

qiita.com

その後、利用ツールは以下の通りいくつか増えていきました。

事業拡大や利用サービスの増加・運用フローの見直しに伴い、これまで利用してきたツールだけではどうにもならなくなるため、別のツールに乗り換えることは多々あります。 しかし、ただ闇雲に乗り換えては後々の運用過負荷に陥る可能性もあるため、自分なりの選定基準を以下のように設けています。

運用負荷や個人依存を減らせるか

まず大前提としてこれを達成できなければ導入する意味はほぼありません(検証は別ですが)。 自分が楽するためだけのツールでれば、Bashスクリプトの書きなぐりをPCに入れておけば済む話だと思っています。

既存の運用を変える必要があるか、その変更に耐えられる組織か

例外としてHashicorp製のツールは非常に汎用的に作成されていると思いますが、ほとんどのOSSのツールは「自社の運用負荷を軽減するために開発した」ツールであることが多いです。 そのため、OSSのツールを利用する場合はその会社の運用を取り込む必要が生まれるケースがあります。 システムの管理的にまたは組織的に変更可能であれば、それに乗っかるのもひとつの手です。

自分以外の人間が使いこなせるか

Infrastructure as Codeは自分以外の他人が使えて初めて価値があるものです。 そのため、ツールの導入コスト・学習コストが高い場合は社内で広まりにくいです。 無理に導入すると変更のたびに導入者に依頼が飛ぶようになるため、結果として個人依存度が下がらない事も考えられます。

OSSであればPRを出す覚悟はできるか

「既存の運用を変える必要があるか」という問いに対して、できないケースもありえます。 これは導入前に発見できず、導入後に明らかになってくることもあります。 そのときはツールの利用をやめるか、OSSに修正PRを飛ばして組み込んでもらうことも視野に入れます。

適切なテンプレート・モジュールの提供

Infrastructure as Codeにおいて、コードのテンプレート化・モジュール化は非常に重要な機能だと考えています。 しかしながら、テンプレート化・モジュール化を適切に提供しなければ、将来の技術的負債となりえます。

これから実際にGunosyで起きたテンプレートにまつわる負債の話と解決方法についてお話します。

GunosyではAWS IAMを codenize.tools/miam でコード化・管理しています。 miamではテンプレート機能が提供されており、ポリシードキュメント等をテンプレートを介して共有することが可能です。

例えば、 gunosy-no-deploy-bucket S3バケット以下の各オブジェクトをGetObjectできるようなポリシーをテンプレートとして定義するとこんな感じになります。

template "s3-deploy-bucket" do
  policy "s3-deploy-bucket" do
    {"Version"=>"2012-10-17",
     "Statement"=>
     [{"Effect"=>"Allow",
       "Action"=>["s3:GetObject"],
        "Resource"=>["arn:aws:s3:::gunosy-no-deploy-bucket/*"]}]}
  end
end

実際にこれを使うには、対象のRole/User内で include_template することで展開され、反映されます。 非常に簡素な記述になり、見栄えがよくなります。

role "nanika-no-role", :path=>"/" do
  include_template "s3-deploy-bucket"
end

このテンプレート機能を使用し始めて間もなく、「特定のシステムのためのテンプレート」が生まれました。 仮に article-classifier.batch という名前のシステムに、 s3://gunosy-no-mecab-ipadic/latest/* のオブジェクトを取得できるような権限を考えます。

template "article-classifier.batch" do
  policy "for-article-classifier.batch" do
    {"Version"=>"2012-10-17",
     "Statement"=>
     [{"Effect"=>"Allow",
       "Action"=>["s3:GetObject"],
        "Resource"=>["arn:aws:s3:::gunosy-no-mecab-ipadic/latest/*"]}]}
  end
end

当然、「article-classifier.batchのためのテンプレート」ではあるのですが、以下のできごとが発生しました。

  • article-classifier.batchではない他のシステムが、「同じ権限が必要」という理由でこのテンプレートを利用し始める
  • もしくは「同じ権限が必要」という理由で中身をそのままコピーして別テンプレートを作成する
  • 「開発で新しく別の権限が必要になった」という理由で、同じテンプレート内に権限が追加される

その結果、以下のような技術的負債を生む結果となりました。

  • 実際の権限を確認するために2段階の確認(そのIAMロールを見て、そのロールに付与されているテンプレートの内容を確認する)が必要となり、時間を浪費する
  • システム名とテンプレート名が不一致で、誰がどのテンプレートを利用しているか追跡困難
  • 同一のテンプレートを利用している、もしくは内容をコピーしてきたため、不要な権限が付与されている
  • 不要な権限かどうか、アプリケーションの内部まで確認しないとわからないため、リファクタリングができない

この問題に対処すべく、「誰のためのテンプレート」という考え方から「何を使うテンプレート」という考え方に切り替えました。

以下のテンプレートはこの問題に対応したテンプレートで、実際にGunosyの実環境で利用されています。

template "readable-s3-within-path" do
  raise ArgumentError.new "Missing argument => :bucket_name" if context.bucket_name.nil?
  raise ArgumentError.new "Missing argument => :path . If you need permission to all pathes, use `readable-s3-anywhere` instead" if context.path.nil?
  raise ArgumentError.new "Invalid argument => :path . Cannot use '*', use 'readable-s3-anywhere' instead." if /^\*$/ === context.path
  policy "readable-s3-#{context.bucket_name}-within-#{context.path.gsub(/\//,'.').gsub(/\*/, 'wildcard')}" do
    {"Version"=>"2012-10-17",
     "Statement"=>
      [{"Effect"=>"Allow",
        "Action"=>["s3:GetBucketLocation", "s3:ListBucket*"],
        "Resource"=>["arn:aws:s3:::#{context.bucket_name}"]},
       {"Effect"=>"Allow",
        "Action"=>["s3:GetObject*", "s3:ListMultipartUploadParts"],
        "Resource"=>["arn:aws:s3:::#{File.join(context.bucket_name, context.path, "*")}"]}]}
  end
end

このテンプレートを要約すると、「特定のS3バケットの特定パスに対してRead系アクセス許可を与える」というものです。 miamのテンプレート機能では、テンプレート呼び出し時に引数を与えることができ、テンプレート宣言内でその引数の情報を利用することができます。 この引数情報をテンプレート内で文字列展開することを発案し、実践しました。

このテンプレートを利用した場合の記述がこのようになります。 前者が内部でどういった権限が付与されているか不透明だった事に対し、後者は記載の簡素さをそこまで落とさずに、具体的なS3のバケット・パスが明記された状態で確認が可能です。

# 変更前
role "article-classifier.batch", :path=>"/" do
  include_template "article-classifier.batch"
end

# 変更後
role "article-classifier.batch", :path=>"/" do
  include_template "readable-s3-within-path", bucket_name: "gunosy-no-mecab-ipadic", path: "latest"
end

このテンプレートを導入し始めてから、同僚に「わかりやすくて使いやすい」という意見をいただいたり、実際にレビューにかかる時間やレビューの正当性も向上した実感があります。

このように、テンプレート・モジュールを適切な形で提供してあげることがInfrastructure as Codeを運用するための重要な心構えだと思っています。

Self-Service Is More Than A Button

最近参加した勉強会のなかで、hbstudy #85 SRE大全: スタディスト編*1 に参加できたことは非常にいい経験でした。 たくさんの貴重なお話のなかでも、「Self-Service Is More Then A Button」という内容に非常に共感しました。

(この資料のp65です) speakerdeck.com

口頭での解説となりましたが、「自分たちのオペレーションを楽にするために何かを作ることよりも、各開発チームに権限を移譲してオペレーションを各自で回してもらう方が組織にとってよい」という考えです。

Infrastructure as Codeを実践するにあたってこの考え方が浸透していれば、SREチーム・開発チームともに(待ち時間を含む)作業時間が少なくなるのではないか、と思っています。

最近では、チーム内レビューアーから2名以上のApproveを条件に、担当チームへ変更権限を移譲しました。 移譲にあたってはSREとして気をつけているポイントをいくつか共有し、積極的に議論を促すようにお願いしました。

これにより、領域は近いのにいままで無関心だったインフラの設定に対して意識を持ってもらえたかな、と思っています。

今後の希望

昨今、数多くのSaaSが誕生しています。 これらは開発速度を上げるために非常に有用なサービスが多い一方で、ユーザ管理・秘匿情報設定・管理対象追加時の運用等、手動のオペレーションが数多く発生する要因となっています。 そのため、これからはSaaS as Codeが非常に重要になってくるのではないかと考えています。

これを実現しているツールとしてTerraformが上げられます。ただ、Terraformの運用が対象SaaS・組織とそぐわない状況も考えられるので、もっと数多くのツールが世の中に提供されてほしい、もしくはOSSとして開発・運用していきたい*2と思っています。

最後に

編集中の記事が吹っ飛ぶ前は記事冒頭で書こうと思っていたのですが、僕の応援するGreenBay Packers*3が今期のプレーオフ戦線から脱落(昨日まではワンチャンあったけど、今日の敗戦でほぼ決定)してしまい、2重のかなしみの中書きました。 2019年のドラフトでは1順指名権が2個あるのと、ディフェンス豊作の年なので素晴らしいルーキーを指名して来年こそはスーパーボウルに言ってほしいです! #GoPackGo

*1:https://hbstudy.connpass.com/event/104830/

*2:余談ですがいくつかのツールにContributeしてきましたが同時にいくつものリポジトリを見るのは大変なので、仕事の一環として正式にOSS活動取り入れたいなぁ

*3:アメリカで開催されるアメリカンフットボールリーグNFLのチーム。おすすめの選手は2018年ドラフト1順指名CBジャイア・アレキサンダー