for Startups Tech blog

このブログのデザインを刷新しました。(2023/12/26)

Figma APIを使用し、svg形式のアイコンを /figma/images 配下にインポートするrake taskを実装してみる

こんにちは、2022年4月にフォースタートアップスにジョインしたエンジニアの八巻(@hachimaki37)です。入社から早半年間が経ちました。引き続き、社内向けプロダクト「タレントエージェンシー支援システム(SFA/CRM)」のシステム開発を担当しております。

はじめに

百聞は一見にしかず!ということで、まずはどんなモノか動画をご覧ください(34秒)

ご視聴ありがとうございました。今回のTech Blogは 、「Figma APIを使用し、svg形式のアイコンを/figma/images配下にインポートするrake taskを実装してみる」です。開発の背景や目的、苦労話などを交えながら執筆していきたいと思います。

事前に様々な記事を拝見させて頂きましたが、アイコンをFigmaで管理する上で、どこも同じような課題感があるんだなと所感を持ちました。

  • アイコンが大量に増えて、管理がめんどくさい
  • 毎度更新されたアイコンをsvgに書き出すのが手間
  • わざわざFigmaを見に行くのがめんどくさい
  • デザイナーがfigma上でアイコンを編集していたことが開発者に伝わらずに、アウトプットが予想と違うものになった
  • アイコンの更新漏れが発生した
  • などなど・・

開発の背景

所属チームでは、2022年7月に新しくUI/UXデザイナーの方を迎え(2022年4月からUI/UXデザイナーが不在)、タレントエージェンシー支援システム(SFA/CRM)のDesign System構築が本格的にキックオフされました。

以下、デザイナーの@Minmi303より得た内容です。

Design System構築に至った経緯

  • 既存のDesign Systemが使われていない(使いづらい)状態だった
  • サービスの詳細、機能、画面全てを網羅している人がいない(網羅するのが難しいほどの画面数)
  • デザインのテイストが2〜3種類ほど混在していた (例:アイコンがデザイナーが作ったものとFont Awesomeが混在していた)
  • 機能追加や削除など、開発のペースが早い
  • デザイナーの効率化を図る仕組みが必要だった(デザイナーが1人のため、作業時間の短縮を図りたい)
  • エンジニアサイドの仕組み化も必要だった(同じパーツでもコードがまとまっていない)
  • などなど・・

Design System構築前に抱えていたUI/UX周りの課題

  • UXを検討するフローが抜けていた
  • デザインレビューのフローがなかった(デザインを担保できない)
  • デザインのトンマナが整っていない
  • 多くの画面がUIを深く考慮されておらず、機能だけがある形の仕様になっていた
  • などなど・・

上記のような背景からDesign System構築が進むにつれ、Colors, Text Rule, Iconsなどが刷新され、開発者用にFixデータがFigmaに格納されるようになりました。

開発の目的

そこで課題に上がったのが、アイコンの格納方法です。

▼議論されたイメージ図

当初、「Figma -> Notion -> タレントエージェンシー支援システム(SFA/CRM)」という格納フローを検討しておりましたが、デザイナー/エンジニア間、双方に課題感を持っておりました。

  • デザイナー: svg書き出しの手間、更新されたアイコンの適応状況 など
  • エンジニア: svgの配置、適用ディレクトリの検討、アイコンの更新漏れ など

結果、Notion管理を不要とし、Figma APIを活用する方針となりました。よって、「Figma -> タレントエージェンシー支援システム(SFA/CRM)」という格納フロー構築を目指し、開発スタートに至ります。

Figma APIとは?

What can I do with the Figma API?

The Figma API supports read access and interactions with Figma files. This gives you the ability to view and extract any objects or layers, and their properties, so you can render them as images outside of Figma. You can then present your designs, connect them to other applications, or use them to expand on your vision. Future versions of the API will unlock even greater functionality around Files.

How does it work?

The Figma API is based on the REST structure. We support authentication via access tokens and OAuth2. Requests are made via HTTP endpoints with clear functions and appropriate response codes. Endpoints allow you to request files, images, file versions, users, comments, team projects and project files.

引用元 公式ドキュメント:https://www.figma.com/developers/api

端的に言うと、Figma API を使用することで、Figma上の様々なデータ取得が可能になります。こちらを用いて、格納フロー構築を行いました。

やったこと

概要

rakeコマンドを実行することで、Figmaからsvg形式のアイコンを/figma/imagesディレクトリにインポートする

大まかな実装イメージ

  • rake taskに以下処理を実装
    • Figma API コールの定義
    • svgダウンロードURLを取得する
    • svgをダウンロードする
    • 生成したファイルにsvgを書き込む

コードの紹介

※アイコンは以下ページから取得することとします

そしてfigma.rakeはこのようになりました

namespace :figma do
  desc 'import svg icons of Figma API'
  task :import_icons do
    # Figma API コールの定義
    file_key = Settings.FIGMA_FILE_KEY
    x_figma_token = Rails.application.secrets.x_figma_token
    figma_url = "#{Settings.HTTPS_PROTOCOL}://#{Settings.FIGMA_HOST_URL}/files/#{file_key}"
    header = { 'X-Figma-Token' => x_figma_token }

    # svgダウンロードURLを取得
    body = get(figma_url, header)
    body['document']['children'].each do |children|
      next unless children['name'] == 'New Icons'

      children['children'][0]['children'].each do |child|
        id   = child['id']
        name = child['name'].gsub('/', '-')
        next unless name.include?('icon-')

        # svgをダウンロード
        svg_download_url = "#{Settings.HTTPS_PROTOCOL}://#{Settings.FIGMA_HOST_URL}/images/#{file_key}?ids=#{id}&format=svg"
        child_body = get(svg_download_url, header)
        child_res = res(child_body['images'][id], header)

        # 生成したファイルにsvgを書き込む
        file = "app/src/javascripts/spa/assets/figma/images/#{name}.svg"
        File.open(file, 'wb') do |f|
          f.write(child_res.body)
        rescue SystemCallError => e
          puts "class: [#{e.class}] message: [#{e.message}]"
        rescue IOError => e
          puts "class: [#{e.class}] message: [#{e.message}]"
        end
      end
    end
  end

  private

  def get(url, header)
    res = res(url, header)
    JSON.parse(res.body)
  end

  def res(url, header)
    url = URI.parse(url)
    Net::HTTP.get_response(url, header)
  end
end

ソースコードの説明を加えていきたいと思います。

Figma API コールの定義

file_key = Settings.FIGMA_FILE_KEY # ※1
x_figma_token = Rails.application.secrets.x_figma_token # ※2
figma_url = "#{Settings.HTTPS_PROTOCOL}://#{Settings.FIGMA_HOST_URL}/files/#{file_key}" # ※3
header = { 'X-Figma-Token' => x_figma_token } # ※4

Figma APIを使用するにあたり、「File Key」と「Access Token」が必要になります。

svgダウンロードURLを取得

body = get(figma_url, header)
body['document']['children'].each do |children|
next unless children['name'] == 'New Icons'

    children['children'][0]['children'].each do |child|
       id   = child['id']
       name = child['name'].gsub('/', '-')

Figma APIをコールすると、

{"document"=>
  {"id"=>"0:0",
   "name"=>"Document",
   "type"=>"DOCUMENT",
   "children"=>
    [{"id"=>"0:1",
      "name"=>"Page 1",
      "type"=>"CANVAS",
      "children"=>
       ・
  ・
  ・
    }]
  }
}

のようなJSON形式で指定したファイルの値が返ってきます。今回Figmaから「New Icons」というページ(上の画像を参照)からアイコンを取得したいため、'next unless'で'New Icons'の値を取得、eachで返ってきた値を変数idとnameに格納します。今回、アイコン名に階層('icon/icon-hoge'のような)があったため、gsubメソッドで置換した値を変数nameに格納する形にしました。

svgをダウンロードする

svg_download_url = "#{Settings.HTTPS_PROTOCOL}://#{Settings.FIGMA_HOST_URL}/images/#{file_key}?ids=#{id}&format=svg" # ※1
child_body = get(svg_download_url, header)
child_res = res(child_body['images'][id], header)

※1 本来は、'https://api.figma.com/v1/images/#{file_key}?ids=#{id}&format=svg"'のようなHTTP Endpointになります

HTTP Endpointには、一つ前で格納したそれぞれのidを、そしてsvg形式で値を取得したい場合、’ format=svg ’を末尾に加え、APIコールをします。すると、以下のような値がレスポンスとして返ってきます。

=> "<svg width=\"108\" height=\"62\" viewBox=\"0 0 108 62\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M53.9998 61.5632C52.0642 61.5632 50.1289 60.8241 48.6532 59.3491L2.21553 12.911C-0.73851 9.95695 -0.73851 5.16748 2.21553 2.21464C5.16838 -0.738212 9.95689 -0.738212 12.9112 2.21464L53.9998 43.3057L95.0887 2.21607C98.0427 -0.736777 102.831 -0.736777 105.783 2.21607C108.739 5.16892 108.739 9.95839 105.783 12.9124L59.3464 59.3506C57.87 60.8258 55.9347 61.5632 53.9998 61.5632Z\" fill=\"black\"/>\n</svg>\n"

これで、svg形式のアイコンを取得することができました。そして、この値を変数に格納し、次のステップへいきます。

生成したファイルにsvgを書き込む

file = "app/src/javascripts/spa/assets/figma/images/#{name}.svg"
File.open(file, 'wb') do |f|
  f.write(child_res.body)
rescue SystemCallError => e
  puts "class: [#{e.class}] message: [#{e.message}]"
rescue IOError => e
  puts "class: [#{e.class}] message: [#{e.message}]"
end

最後に、一つ前で取得したsvg形式のアイコンをファイルに書き込みます。1行目(file = xx)には、変数nameを加えたファイルを定義し、書き込みにはFileクラスのopenメソッドを使用しました。あとは、取得したsvg形式のアイコンをwriteメソッドを使用して、指定したファイルに書き込めば完了です。

はまった点

チケット完了のゴールは定まっていたものの、そもそも実装イメージが皆無だった

  • Figma APIとはなんぞや...
  • どのようなTODOで進めて行けば良いのだろうか..
  • Figma自体さほど触っておらず、理解が浅い...

そんな状況からスタートしました。やったことは、開発着手前に「Figma APIの公式ドキュメントを一読する」、「現時点で思いつくTODOを洗い出す」ことから始めました。今までの経験から、プログラミングで右も左もわからなくなるケースは、大体何をすべきか理解できていないことが多かったため、着手前に思いつくTODOを洗い出し、都度TODOを追加していくようにしました。一例ですが、「Figmaにテストデザインを作成し、そのデザインをFigma API をコールして取得する」ことをTODOに含め実施したことで、「Figma APIの理解が進んだこと」、「やるべきことが都度明確になっていったこと」で、実装イメージが膨らみ、進めることができました。

svg形式のアイコンを取得したかったが、HTTP Endpointを叩くとレスポンスの値がnilになる

以下のようなHTTP Endpointをコールすることで、svgに置換された値がレスポンスされるはずでしたが、なぜかnilになっていました。

'https://api.figma.com/v1/images/#{file_key}?ids=#{id}&format=svg

pry(main)> child_res.body
=> nil

調査を進めていくと、’ ids=#{id} ’に格納した id の値が誤りであることがわかりました。APIのレスポンス値である’ components id ‘を格納していたことがnilの原因であったため、各アイコンに紐づく id に変更することで解消されました。

工夫したこと

今後の運用を考慮したこと(なるべくシンプルに保つ)

運用についてデザイナーと議論し、以下のことを決めました。

  • アイコンの命名規則
    • 基本的に「icon-」の形式とする
  • アイコンのツリー構造
    • 基本的にノード同士は親子関係にはせず、並列とする
  • アートボードの運用
    • 基本的に1アートボードにアイコンを集約する。もしアートボードを追加する際は、エンジニアに一報を入れる。(複数のアートボードからアイコンを取得する場合、データ構造を見直す必要の可能性があるため)
  • exportされるページへの注意喚起
    • 後任者などが見ても理解できるようFigmaのページ上に注意喚起を追記する

ソースコードの汎用性や可読性を考慮したこと

  • 汎用的に使用できるよう、Figma APIコールの定義をsettingsにまとめました。また、X_FIGMA_TOKENは作業者で異なるキーを運用するため、「gitの管理対象ファイルにして、間違えてコミットしてしまう可能性」を考慮し、.gitignoreにあるファイルで管理するようにしました

  • 処理のメソッド化、直感的に理解が進むよう必要最低限のコメントを追記、またソースコードを処理ごとにまとめ実装しました

改善余地があること

Access Tokenの運用方法

APIコールに使用するAccess Tokenに関して、チームで1つのTokenを運用するか、もしくは個人個人のTokenで運用するか、別途議論の余地がありました。所属チームの現時点での運用は、個人個人での運用とし、必要に応じて議論する形にしました。

image optimizerの検討

画像圧縮についてです。理想は、APIコールからsvgをダウンロードした際、プログラム上で画像圧縮が実行できればベストでしたが、優先度の兼ね合いから今回のチケットではスコープ外とし、対応可否を別途検討する形にしました。

Github Actionsでの定期実行

手動でコマンドを叩くこと自体が課題になるかもしれません。こちらも今回の対象チケットには含めなかったため、定期実行をScheduleに組込み、自動実行できる仕組み構築が今後必要になってくるかもしれません。

まとめ

実装から無事リリースに至り、これから運用へとプロセスを踏んでいきます。運用しながら課題が新たに発生するかもしれませんが、ひとまず元々感じていた双方の課題を解消できる開発になったのではないかと思います。

あとがき

私自身、今回初めてFigma APIに触れましたが、思いのほか手軽に実装することができました。というか、初めてでもわかりやすい仕様だったということでしょう。デザイナー、エンジニア双方の課題感を知り、実装を進められたこと、非常に楽しくワクワクしながら開発できました。

最後に採用情報です。 当社では、まだまだ採用募集中です。ぜひ一緒に成長していきませんか。 ご興味ありましたらぜひ一度カジュアルにお話できたらと思います。

採用ページはこちら。(冒頭のTwitterにDM頂いてもOKです!)

参考資料

スプリントレトロスペクティブ本来の目的とは?初めてファシリをやって体感した「難しさ」と「学び」

こんにちは、2022年4月にフォースタートアップスにジョインしたエンジニアの八巻(@hachimaki37)です。主に社内向けプロダクト「タレントエージェンシー支援システム(SFA/CRM)」のシステム開発を担当しております。

今回は、スクラムのイベントの一つである「スプリントレトロスペクティブ(以下、レトロスペクティブ)」について書いていきたいと思います。

早速ですが、レトロスペクティブをやっていて、こんなこと思ったことありませんか?

  • 最近なんとなくやってるなー
  • テーマを出したいけど、これで大丈夫かな..(迷う..)
  • TRY実行してるけど、何が改善されたんだ?
  • チームで議論すべきテーマってこれでいいの?
  • ふりかえりやだなぁ..

みたいな・・

ぜひこの機会に1つ考えて頂きたいことがございます。 それは、「あなたは、なぜレトロスペクティブ(ふりかえり)を行っていますか?

そんな「なぜ」を解消する内容にしてみました。

レトロスペクティブとは?

スクラムチームは、個人、相互作用、プロセス、ツール、完成の定義に関して、今回のスプリントがどのように進んだかを検査する。多くの場合、検査する要素は作業領域によって異なる。 スクラムチームを迷わせた仮説があれば特定し、その真因を探求する。スクラムチームは、スプリント中に何がうまくいったか、どのような問題が発生したか、そしてそれらの問題がどの ように解決されたか(または解決されなかったか)について話し合う。

スクラムチームは、自分たちの効果を改善するために最も役立つ変更を特定する。最も影響の大きな改善は、できるだけ早く対処する。次のスプリントのスプリントバックログに追加することもできる。 スプリントレトロスペクティブをもってスプリントは終了する。スプリントが1か月の場合、スプリントレトロスペクティブは最大 3 時間である。スプリントの期間が短ければ、スプリントレトロスペクティブの時間も短くすることが多い。

引用元:スクラムガイド

これを読んで考えたことは、「スプリント中に何がうまくいったか、どのような問題が発生したか、そしてそれらの問題がどのように解決されたか(または解決されなかったか)について話し合う」ことはあくまで手段であり、「自分たちの効果を改善するために最も役立つ変更を特定する」ことが最も重要だということです。

後述しておりますが、当初私が考えていた重要なことは、「スプリントであった出来事を整理し、チームの課題を改善すること」でした。ここに大きなギャップがありました。

所属チームのふりかえり手法と決め方について

所属チームでは、2週間のスプリント最終日にレトロスペクティブを開催し、経験年数に関わらずサイコロを使いランダムにファシリテーターを決めています。ファシリテーターに決まった方は、自身でふりかえり手法や段取りを考え実施します。

当社のレトロスペクティブの歴史としては、特定のファシリテーターがおり、基本的にKPTにてふりかえりを行っていたみたいですが、日々改善される中で今の形へと変わっていきました。

実施したレトロスペクティブの紹介

デイリーハッスル

デイリーハッスルは、「日常の慢性的なわずらわしい出来事を解消していくふりかえり手法」です。ストレスマネジメントの権威であるLazarusとFolkmanが1980年代に提唱した概念で、ストレス心理学におけるストレッサーの区分モデルのひとつです。

ラクティスとしてのデイリーハッスルは、この記事を参考に実施させていただきました。

引用元:ふりかえり手法のおもちゃばこ(前編)

  • 実施結果

Time Line

Time Lineは、「事実と感情の両方を合わせて書き出していき、全員で共有するふりかえり手法」です。書き出した事実を時系列に並び替え共有することで、チームの持つ情報を整理し、カイゼンのためのアイデアを出しやすくします。

  • 実施結果

工夫したこと

チーム状況に合った手法を選択

  • デイリーハッスルをやった時期は、新しいメンバーが加わったこともあり、身の回りの問題に焦点を当てられるふりかえり手法を選択しました。また合わせてメンバーの価値観(どんな場面でデイリーハッスルを感じるのか)を共有できるチャンスでもあったため、目的に加えて実施しました。
  • Time Lineを行った週は、イベントが多いスプリントであったため、日々やったことをチームで整理できるふりかえり手法が良いかと考え、Time Lineを実施しました。

Notionを活用

チーム内に出社組・リモート組がいたため、場所に囚われないようNotionを活用しました。ホワイトボードを活用する機会が多かったですが、ふりかえり手法によってはNotionも使用可能だと成功体験を得ることができました。

付箋の色分け

Time Lineを行う際に、付箋の色を分けたことで「事実」と「感情」の判別がしやすくなり、スムーズな進行が可能になりました。

体感した「難しさ」と「学び」

議論すべきテーマの選択

ふりかえりの議論となったテーマが、チームに対する共有事項であったり、影響範囲がチーム外の事柄、個々人によりすぎたテーマとなってしまいました。良し悪しというよりは、何のためにレトロスペクティブをやるべきなのか?を考えるきっかけになりました。

本当にそのTRYでProblemを解消出来るのか

つまり、TRYの精度です。TRYのアイディアが出されるも「本当にProblemを解消出来るTRYになっているのか?」を考えすぎてしまいました。正直やってみないことには何もスタートしません。そのため「決めたTRYを実行し、改善を繰り返す」という目線に変え、TRYを決めていきました。

レトロスペクティブの目的とは?

私が考えていたレトロスペクティブの意義

  • チーム課題を改善すること?
  • チームで洗い出したProblemを改善すること?
  • スプリントであったことを整理すること?
  • スプリントをふりかえること?
  • チームで決めたTRYを実行すること?
  • ...

私が考えていたレトロスペクティブの目的(意義)は、「スプリントであった出来事を整理し、チームの課題を改善すること」だと考えていました。しかし、スクラムガイドを再読した際に、レトロスペクティブ本来の目的を理解していなかったことに気づきました。

レトロスペクティブ本来の目的

スプリントレトロスペクティブの目的は、「品質と効果を高める方法を計画すること」である。

引用元:スクラムガイド

つまり、「チームがもっと効率を高めることができるTRYを計画できたか」です。

重要なファクター

スクラムガイドでは、以下2点が重要であると述べられているように思います。

  1. スプリント中に何がうまくいったか、どのような問題が発生したか、そしてそれらの問題がどのように解決されたか(または解決されなかったか)について話し合う。 ◯所属チームは、現状できている
  2. 自分たちの効果を改善するために最も役立つ変更を特定する。 ✖️所属チームは、現状できていない

これらを通して思ったことは、目的を意識することが大切だということです。「目的を理解していない = いつまでたってもゴールにたどり着くことはできない」。つまり、品質と効果を高められるTRYを計画できるにはほど遠いという事実を知ったことが、今回感じた最大の学びでした。

今回得た学びを実践した結果をふりかえる

今回の学びを通じて、個人的に決めた3つのTRYを実行してみました(3に関しては、時間の都合でできず..次回のTryとして実施していきたいと思います)

  1. レトロスペクティブの目的をチームへ周知する
  2. 自分たちの効果を改善するために最も役立つであろう変更を議論のテーマとする
  3. 品質と効果を高められるTRYを計画できたかをふかりえり後にチームに確認する

まず1に関しては、事前にタイムスケジュールを組み、レトロスペクティブ開始前に実施しました。やったことは、今回得た知見をチームへアウトプットするといった内容です。

共有自体はさほど難しいことではありませんが、やはり最大の課題は2の「最も役立つであろう変更を議論のテーマとする」ことでした。一人3票、議論したいテーマに各々投票する形式にしましたが、普段よりも投票に時間がかかった印象でした。チームとして、何を改善(議論のテーマと)すれば、「チーム効率がもっと高まるのか」を考える必要があるため、いつもよりも俯瞰的に物事をみる必要があったかと思います。

ご参考までに今回議論となったテーマです。

  • 朝会の運用をもう少し改善できないか?
  • 開発途中で仕様変わってない?

改善活動1回目のふりかえりの場でしたので、新たな思考のきっかけを今回作れたのではないかと思っております。引き続き継続していきます。

まとめ

Q1. レトロスペクティブ(ふりかえり)の目的は?

A.「品質と効果を高める方法を計画する」ことです。

Q2. レトロスペクティブ(ふりかえり)では、どんな議論をすべきか?

A.「スプリント中に何がうまくいったか、どのような問題が発生したか、そしてそれらの問題がどのように解決されたか(または解決されなかったか)について話し合うこと」です。

Q3. その理由はなぜか??

A.「自分たちの効果を改善するために最も役立つ変更を特定する」ためです。つまり「チームがもっと効率を高めることができるTRYを計画する」ために必要なファクターなのです。

あとがき

当社開発チームは、現在2つのチームで開発を行っています。今回ブログを執筆する前に共通のLT会でレトロスペクティブに関する発表をさせて頂きました。

所属チームは、2週間のスプリント最終日にふりかえりを行い、改善活動を行なっています。

良いスプリントだったとふりかえることもあれば、改善多数とふりかえることもあります。さまざまな変数があるかとは思いますが、効率を高められた!良いTRYが実行できた!素敵な価値をユーザへ提供できた!というようなポジティブな状態をチームで作っていけるよう、日々の改善活動を継続して行なっていきたいと思います。

最後に採用情報です。

当社では、まだまだ採用募集中です。ぜひご一緒に良い組織・チームを作っていきませんか。 ご興味ありましたらぜひ一度カジュアルにお話できたらと思います。

採用ページはこちら。(冒頭のTwitterにDM頂いてもOKです!)

スモールチームのインフラ担当・SREとして入社し取り組んだことと失敗したこと

こんにちは。フォースタートアップス株式会社でインフラ・SREを担当している吉田です。昨年入社しました。 今回は、インフラ・SRE担当の一人目として入社してからこれまでの約1年間で取り組んできたこと、失敗してきたことや課題をお話します。

入社時の弊社の状況

入社したのは約1年前。当時はオーナーや開発メンバーなど含め社員3〜5人のチームが2つあり、それぞれ別のサービスを開発していました。 社員の他にも、チーム内にはパートナーやインターンの方々もいて、約十名が曜日で入れ替わりながら参加いただいていました。

組織の状況は以下でした。

  • 各チーム各サービスとも、開発も進み機能が徐々に増えてきた
  • より開発や運用の安定性をより高めていきたい
  • サービスレベルの目標設定もしたい

そんな組織の、インフラやSREの担当の一人目としてジョインしました。 入社前は2人チームとお話をもらっていましたが、諸事情で専任の一人目となり、インフラやSREの業務をゼロからスタートすることとなりました。

当時はこの2つのチームを横断して、インフラ関連業務やサービス開発と信頼性の向上に取り組むこととなりました。

入社してから取り組んだこと

直近の活動方針を決める

同じ業務をするメンバーもおらず、右も左もわからない状態からのスタートだったので、

  • まずは、メンバーとサービスを理解しよう
  • そこから、今困っていることを探して改善/整備しよう

と、ざっくり活動方針をたてて、以下の直近の活動内容をつくりロードマップをひきはじめました。

  • サービスの現状理解する
    • アラームやインシデントの対応と棚卸し
    • サービス目標と追いかける指標の策定
  • 整備と改善

上の内容に取り組む中で、良かった点と失敗した点がありました。

アラームやインシデントの対応と棚卸し

着任してから、まずは、

  • サービスの開発や運用の状況や構成を知ろう
  • 安定性を高めるためにはどこからなにをはじめるか、ポイントを探ろう
  • 今サービスの安定をおびやかす事象にどのようなものがあるのか知ろう

という考えのもと、

  • サービスを知るには、まずは日々のアラームや障害の対応にガンガン参加することだ!
  • チームが今、何を大事にして何を追っているのか、まずはつかむぞ!

という思いで動き出しました。

...というのは半分建前で、実はその裏では「早く馴染みたい、早く役に立ちたい」という焦りや不安がとても大きかったのです。

というのも、各チームは人数が少ないこともあってか、

  • チームの方針に則りながらも各メンバーが自律的に積極的に動いている
  • 誰かがアサインしなくても開発Issueがどんどん上から解決されていく
  • また、サービスの問題もチームで話し合い分析し、Issue化して協力して取り組むことができている

という、自律しながらも協力して取り組む文化のある、統率のとれたチームでした。

このチームとともに活動をしていくためには、

  • 自分も早くIssueを取れるようにならないといけない
  • 早く各チームのメンバーに、一員として認められたい
  • この無力感を脱したい

という焦りを感じていました。

そのためには「四の五の言わず、まずは現状把握と対応だ!🔥」という(勝手な)思いがありました。

失敗:入社直後はなかなか役に立てず空回り

まずは各チーム各サービスのアラームやインシデント対応に参加をはじめます。が、しかし早速つまづきます。

  • 発生するアラーム、発生する事象が、日々とても多い
  • 発生している事象が、どこの何が起因なのかがつかめない
  • サービスを構成するサービスが複数存在し、データストアの相互参照もあり
  • 対応を優先するべきアラームなのか、対応が不要なアラームなのかの判断がつかず、勝手に右往左往してしまう

既存のメンバーはそのアラームの過去の発生頻度や様子から、発生箇所の特定やおおよその原因を掴んで進んでいきます。

私はサービスの理解が浅いことも有り、問題の概要がつかめずに対応の初動まで時間がかかってしまいます。その間にまた別のアラームや事象が発生します。

  • アラームがなる、とりあえず確認する
  • これどこのなんのサービス?影響範囲は?前後の事象と関係がある?
  • これはなに?これは見るの?これは見なくていいの?
  • と思っていたら、別のアラームが...

入社して数日は素早い対応がなかなかできず、悔しい日々が続きました。

気づきと学び:まずは状況観察とヒアリングが大切

日々発生する事象の対応をしながら、少しずつですがサービスの概要や業務の流れを掴み始めました。

同時に、xxxの情報があったらもっと詳しい調査ができるのではないか、原因不明の事象もxxxのログから追えるのではないか、と、一歩引いた目線でインシデントを振り返る時間もできました。

最前線で対応することはもちろんですが、この小さいチームで「SREとして、対応者や門番の役割をさらに超えてチームと伴走し、よりサービスの信頼性をあげるには今何が必要なのだろうか」を考え始めました。

開発メンバーと同様に対応をはじめるだけでなく、別の観点やアプローチをつくることでチームとしてより早く問題解決ができるのではと感じ始めます。

対応人数が少ないからこそ、発生する事象が多いからこそ「まずは状況を観察し判断すること」、インシデントの対応にも言わばトリアージのようなものが一定は必要であると感じました。

実際複数のアラームやインシデントが同時に発生することは少なくなく、その場ですぐに優先度をつけることを求められる場面がありました。

複数の対応の優先度付けや対応箇所の判断ができるようになるためにも、今この初期のタイミングで観察や事象の整理をしなければいけないと気づきました。

このことから、インシデントの検知アラームや障害の対応と並行して、事象や通知を棚卸しをしながら、優先度付けや原因特定がしやすくなるように、以下に取り組みはじめました。

  • アラームや通知は精査をして不要なものは流さないこと
  • アラームや通知情報を意味ある情報にすること
  • 本当の関係者に通知すること(サービスも事象も多いが人数は少ないため、担当に注力できるようノイズを極力減らす)
  • 属人化しないよう、誰が見ても同じように状況を認識して動き出せるようになること

例えば、特定の指標の増減や稼働率低下などのSLOを満たしていない事象を通知する場合においても、少し極端な例ですが、


稼働率が下がってきた!やばい!
< 応答速度があがってきた!やばい!

と通知をしても、メンバーは


< 具体的に何が起きてるの?
< サービスのどこがおかしいの?

となり、対応の初動に時間がかかってしまうことが多いです。

なので、そうではなく、例えばですが、


<xx時にxxのエラー発生でユーザにエラー画面が見えている
<xx日からxxのAPI応答時間が延びている

と通知をすれば、メンバーは、


< xxのAPIだから、影響範囲はxxだね
< xx日ということは,もしかしてxxのリリースがきっかけかも...

と、起きている事象の理解や当てをつけることがしやすくなります。

必要な情報を取捨選択して、プロダクトやアプリケーションの詳細な出来事に落とし込み、事象解決につながる情報にして届けることが大切なのだと、当たり前のことではありますが再認識しました。

とすれば、アラームや通知内容は、目指すサービスレベルをおびやかす(SLO違反)警告となるものにするべきです。

今思えば、後述するサービスレベルや追いかける指標(SLI/SLO)の精査に、より早く着手するべきだったと反省しています。

またSLO違反の際に各自がどのように動き出すべきなのか、関係者同士である程度でも目的や方針などの認識をそろえておくともっとスムーズに動けるはずです。

ロードマップや目下の開発を進めることとのバランスもスコープに入れると、メンバーは動きやすくなりますし、チームの心理的安全性の向上にもつながるのではと思います。

良かったこと:現場だから生まれるコミュニケーション

前述のような失敗はありましたが、一方で入社直後で不慣れなことが多い中でも、アラームやインシデントの対応がきっかけでうまく進み始めたことも多くありました。

入社3日目と4日目に続けてインシデントが起きました。具体的には、Elasticsearchの高負荷やノードのダウンによる遅延やエラー応答が発生したのですが、これがきっかけで入社後すぐにチームのメンバーと仕様の深い話をし始めるきっかけができました。

自分 「ちなみにxxxってどうなってるんですか?」
メンバ「これはxxxとxxxでつかっているんだけど、負荷かかってるんだよねー」
自分 「そしたらxxxの部分を見てみますね、ちなみに普段どうやって確認されていますか?」

また、その会話をきっかけに、

「xxxの実行時間と頻度って変更できるものですか?」
「xxxのパラメータで改善できそうです、サービス影響を相談させてください。」
「xxxの作業するので、夜間作業に立ち会っていただけませんか?」

というように、一緒に作業をするきっかけも作ることができました。

対応者として前線に立つことによって、参加できる会話や見える課題もあるのだなと感じた瞬間でした。

サービスの目標と追いかける指標の策定

入社のタイミングで「各サービスの目標(稼働目標など)を定義したい」という課題があり、こちらにも着手しました。

私が入社前からエラーの発生数や応答時間のメトリクスが作成されており、ダッシュボードでそれらを見るという文化もありました。

この文化にも助けられました。どんな指標を大事にしていたか、何に問題意識をもっていたか、がなんとなく掴めたからです。

一方でメンバーからは、各指標の値はわかるが、サービスがどんな状態なのかわからない、何か良い方法はないかと相談を受けました。

そこで、サービスと構成を調べながら、

  • サービスが正常に動いているというのはどんな状態なのか
  • サービスが正常ではない状態は、どのように観察/検知できるか

を関係者と話し合い、取り急ぎ第一弾として、その条件からシステムの稼働率(≒ SLI)を定義しました。

定義後はチームで稼働率を追っていけるように、稼働率と関連項目を確認するためのダッシュボードを作成し、情報共有とモニタリングをはじめました。

ダッシュボードの表やメトリクスは、指標の数値や推移に加えて、現在目標ライン(≒ SLO)を上回っているのか下回っているのかがぱっと見てわかるように、状態に応じて表示色を変えたりメトリクス内にしきい値を描画したりするなど、まずはわかりやすさを重要視しました。

サービスが正常に動作し価値を届けられているのはどのような状態か、また現在それが目指すレベルにあるのか、チームで共通認識を持ちはじめられたことが良かったです。一方で、見える化したことによる副作用もありました。

良かったこと:サービスの現状理解が進み、稼働に対する意識が高くなった

まずは見える化したことで、以下の気づきや変化がありました。

  • 想像以上に未応答数や不安定な時間があることがわかった
  • エラーや未応答に意識が向くようになった

既存のダッシュボードでも、その瞬間の応答状況は確認できていましたが、メトリクスで推移を見ることで過去分も確認できるようになりました。

その結果、想像以上に応答率が落ちていたり遅延していたりするタイミングがあることをチームで共有できました。

今では、リリース時の確認や、朝会などでの定期的な確認をするようになり、稼働(サービス提供)への意識もより強くなりました。

失敗したこと:SLO違反との向き合い方の意識合わせ不足

稼働への意識が強くなったことは良かったのですが、現在のサービスとチームでは初めてのSLI・SLOの制定であったこともあり、どうやって運用していくかまでは気がまわっていませんでした。目標値を下回った(SLO違反が発生した)場合のチームの動き方まで、チームと話ができていなかったことが最初の反省点です。

現在のチームが、サービスに対してとても思いが強く責任感のあるチームだったこともあり、そんなチームにとっては「SLOに違反している ≒ 価値が提供できていないユーザがいる」があることはインパクトのあることでした。

そのため、設定当初はメトリクスの上下や一件のエラー発生の通知すべてに反応していました。

一方でサービスの性質上、ユーザーも利用環境も非常に種類が多く攻撃や不正なアクセスも少なくありません。それもあって、見える化をした直後は、認識していなかったエラーも観察されると同時に検知機構も過敏に反応し不要な通知をするなど、粗い部分もありました。

当時はエラーとして検知しても、本当に内部で発生したエラーである場合もあれば、存在しないもしくは許可されていない箇所への不正アクセスである場合もありました。そうすると、対応に疲弊してしまったりアラームや通知がオオカミ少年のように認識されてしまったりすることもありました。

どのエラーが本当に対応しなければいけないもので、どのエラーが誤検知や対応不要なものであるかを、導入の初期段階ではまだ精査しきれてはいませんでした。

エラー検知やSLOの基準となる指標の上下に必要以上に一喜一憂することなく、状況を把握して冷静に対応するためにも、SLOや指標の通知やメトリクスをどのように業務に組み込んでいくのかの認識をあわせることがまずは必要でした。

特にSLOを設定したばかりでチームに馴染むまでは、SLOは何を目的として設定しているのか、満たせている場合はどんな状態で、満たしていない場合はどんな状態で何をしなければいけないのか、を言語化して共通認識を持てるように共有することが必要でした。チームに根付かせるプロセスをもっとしっかり踏まなければいけなかったと反省しています。

失敗したこと:設定後の運用にまで手が回っていないこと

また、指標や目標の見直しまで手が回っていないことも反省点のひとつです。

新しい機能の提供や新しい形式でのコンテンツ配信が増えれば、それぞれに応じて指標や目標ラインも変わるはずです。例えば、APIとHTMLや動画と音声では、それぞれユーザに応答が届くまでの目標時間も異なるはずです。また新しい指標が必要になることもあるはずです。

また、稼働率が低下した要因を追えるようなモニタリングも当時は考慮できていませんでした。例えば内部エラーの検知や発生時のリクエストの詳細情報などです。

SLIやSLOの運用や見える化が、サービスの変化や成長に追いついていませんでした。

特に初回に設定した目標は、しばらくは定期的に確認と検討をする機会を作るべきでした。アップデートを踏まえたサービスの提供価値とそれを満たす状態を見直す機会を設けていきます。

ボトルネックの解消

サービスの状況を見える化した後は、運用やシステムのボトルネックを見つけて解消することに着手しました。

アラームやインシデントの棚卸しと稼働状況の見える化で、性能低下のタイミングや業務コストが高い箇所がつかめたので、その原因やボトルネック箇所の特定の解消、そのためのデータやログの収集や通知に取り組みました。

具体的には以下に取り組みました。

たとえば、特定の時間帯の応答時間の増加をダッシュボードで確認したため調査をはじめたところ、ログやトレース情報からデータ登録時にエラーと数回のリトライの発生を確認しました。参照もそれに引っ張られる形で影響をうけ、アプリケーションの応答速度に影響がでていました。

詳しい内容と原因特定のため、アラームやトレース情報などのモニタリング項目を追加し事象の調査しました。

調査の結果、弊社ではElasticsearchを利用しているのですが、そこでリクエスト過多のエラーが発生していたため、データ更新量やスレッドプールの状態を観察して処理を特定し、データ登録方法やクラスターの設定を見直し、応答速度の増加を解消することができました。

結果的にアラームの見直しやモニタリングが、ボトルネックの発見と解消につながったのは良かったです。

脆弱性の対策

以下のような脆弱性の対策にも着手しました。

  1. 脆弱性スキャンのツールやサービスの利用
  2. 外部の診断会社による診断の実施
  3. 定期的に脆弱性情報を収集/確認
  4. クラウドやネットワーク設定の確認

1と2については、外部の診断会社や各種サービスを利用して、サーバ/コンテナやアプリケーションに脆弱性がないかスキャンをはじめています。

3については、開発言語やライブラリに関連する脆弱性を、手動でピックアップし調査しています。が、後述しますが、まだうまく運用を回せていません。

4については、クラウドやネットワークに誤った設定(意図しないポート公開やアクセス設定など)がされていないかをチェックしています。

都度確認するのは難しいため、事前にポリシーをつくり、そのポリシーに則っているかをスキャンするようにしています。 このポリシーの作成と運用についてはAWS Config とAWS CDKを用いた手法で取り組んでいます。以下の書籍にまとめていますので、よろしければご覧ください。 https://techbookfest.org/product/5762421668970496?productVariantID=6028076402081792

難しかったこと:活動の必要性や必要なコストの妥当感について理解を得ること

特に2については、診断にかかるコストは安いものではありません。金銭はもちろんですが、環境やデータ準備や診断会社からのヒアリングへの対応もあるため、SREだけでなく開発チームにも負荷はかかります。

1や3のスキャンや情報収集も運用コストがかかります。ユーザに提供価値として直接目に見えて届くものではなく、効果の実感につながるまでに時間も機会も必要です。

意味のある脆弱性対策として機能させるには、継続的な活動と活動を行うための一定のリソースが必要になります。

また、せっかく調査して得た脆弱性情報も、サービスに与える影響に落とし込めないと、情報が流れても誰にも有益な情報としては伝わりません。

「私たち(のサービス)にどう関係あるの?」になるのは当然です。


< xxxでディレクトリトラバーサルの危険あり!
< xxxでオーバーフローの脆弱性が見つかった!


< うちのサービスに関係あるの?
< どのサービスがどう攻撃されるの?

人も工数も少ない小さなチームの中で、継続的な活動にコストを使わせてもらうためには、チームに活動の意義やコストの妥当性が伝わっていることが必要なのだと感じました。

良かったこと:脆弱性に関する書籍の輪読会をおこなったこと

弊社では毎週書籍の輪読会をおこなっているのですが、上の課題に取り組む時期に、輪読会で書籍「体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践」を読む機会をつくることができました。

  • どんな脆弱性や攻撃手段があるのか
  • アプリケーションにどのような影響があるのか
  • 対してどのような対策が必要なのか

輪読会でこれらについて学び議論し意見交換をすることができました。

脆弱性の概要や種類を体系的に学ぶことはもちろんですが、自分たちが開発しているサービスではどんな危険性がありどんな対策が必要なのかを、議論しながら深堀りができました。

この輪読会のおかげで、必要性の認識や対策意識について共通認識を作り始めることができました。とてもありがたかったです。

ちなみに、弊社ではビブリオバトルで輪読会の書籍を決めます。ちなみにこの安全なWebアプリケーションの作り方を推薦したのは私です。https://tech.forstartups.com/entry/2022/03/11/110557

課題:スキャン結果の確認と対応のコスト確保

その後、対策の活動をはじめたのですが、スキャン結果をいかしきれず重要度が高くないものは対応が先送りになるケースも少なくありません。

特に実際に4つの脆弱性対策の施策をはじめた直後は、重要度が低いものも含めて大量に検出してしまい、どこから着手すべきかわからなくなるほどでした。

また、3については、開発言語やライブラリに関連する脆弱性を手動でピックアップし調査していました。ですが、どうしても時間がかかってしまうため、今は大きい脆弱性が見つかった場合に個別で調査するにとどまっています。

1と4で、CI/CDへの組み込みや定期スキャンを実施し、2で外部の専門の会社に診断を依頼してカバーしてもらいつつ、緊急性の高いものや詳細な調査が必要なものを3で調査する、という運用にとどまっています。

スキャンをする方法はそろってきたので、スキャン結果取得から対応までに時間が空いてしまわないように、情報の整理と対応を効率的にできるように改善していきます。

利用SaaSの管理

インフラのリソースとあわせて、SaaSクラウドサービスのアカウントやコストの管理もはじめました。

  • メンバー一覧作成(メール・Githubアカウント)
  • アカウントの発行と削除
  • アカウント発行/削除方法のドキュメント化
  • 毎月の請求業務
  • プランやアカウント変更の見積もりと稟議申請
  • 新しいSaaSを利用するときの確認や申請、法務(リーガルチェック)連携など

開発で多くのSaaSを利用している一方で、各チームにインターンやパートナーの皆さんがいる関係で、不定期にメンバーの増減があります。

アカウント発行が必要なタイミングで、シートが増えてプランも変わり決済が必要となるため、社内の稟議申請も必要になります。

利用しているSaaSをメンバーの情報と一緒に管理をしはじめ、SaaSの利用が必要になったタイミングで必要な権限のアカウントを発行し、費用の増減も合わせて記録・申請する形式で運用をはじめました。

良かったこと:アカウントとコストの管理が一緒にできることの効率性

前述のとおり、メンバーの増減が不定期にあるため、アカウント発行とプランや料金変更も不定期にあります。

都度、チームメンバーが稟議を作成して申請業務をしていては、業務のスイッチングコストも増えてしまいます。担当者が曖昧な状態で、申請や管理業務が漏れていたこともありました。

金額と利用状況をあわせて管理することで、チームで見たときにコミュニケーションや申請業務のコストも抑えられると考えました。

また、不在時に急ぎ必要な場合も、用意した手順書でアカウント発行/削除できる運用にしておくことで、管理人数が少なくとも対応ができるようにしておけば

メンバーが少ない組織でも、各チームを横断して契約や管理ができるようにしておくことで、少しは手間やコストを抑えながら管理できているかなと思っています。

課題:アカウント発行のタイミング

一方で、SaaSのアカウントをいつ発行するか、運用に迷っていることもあります。

  • チーム参画時にアカウント一括で発行
  • 必要になったタイミングで都度発行

「チーム参画時にアカウントを一括で発行」すれば、アカウント数や費用も見積もりがしやすく、アカウントの登録や削除も利用しているSaaSすべてについて実施するため、ある程度決まった手順で実施ができるため、管理の手間が省けるはずです。アカウント管理のサービスも使いやすいはずです。

一方で業務内容やポジションによって、利用するSaaSに違いもあることから、アカウント発行してもメンバーによっては利用が少ないSaaSもあります。

また、アクティブでないアカウントが気づかずに不正アクセスされるケースもゼロではないと思います。

職種やロールによって、アカウント発行のパターンを作ることも検討しましたが、パターン化することも簡単ではなく、結局必要になったタイミングで追加することになりそうで断念しました。

「必要になったタイミングで都度発行」することで、不正アクセスや使用が少ないアカウント分のコストも抑えることはできますが、アカウント発行の作業は都度不定期に発生しますし、誰に何を発行したか記録しておかないと、全サービスを確認する必要があるためアカウント削除作業の手間が増えます。

現在は、メンバーが多くないことも有り、SaaSの費用削減をねらって「必要になったタイミングで都度発行」の方法をとっています。

誰にどのSaaSアカウント発行をしたか記録する目的も兼ねて、SlackにSaaSアカウント管理の専用のチャンネルを作成して運用しています。

これで、発行依頼や削除依頼にある程度機械的にルールを設けることで手間を減らすことを試みていますが、アカウント周りについてはまだまだ改善しなければいけないことが多いです。

ポストモーテム

これは私がはじめたものではなく前任の方が導入してくれていたのですが、月に一度ポストモーテムの場を設けてチームをまたいでインシデントの振り返りをしています。こちらも入社後に引き継いで継続しています。

専用のリポジトリを用意しており、インシデントの対応が完了した後にリポジトリにPRを作成しフォーマットにそって記録する運用としています。フォーマットはSREの書籍の付録にあるものとほぼ同じです。

月に一度、PRを元に各チームから発表してもらう形式をとっています。

失敗したこと:発表するメンバーの負荷の考慮不足

発表する側の負荷の高さが課題のひとつです。

現在のフローは、以下のようになっており、担当者が自主的に情報をまとめレポートを作る必要があります。

  • インシデントの対応完了
  • 担当者がポストモーテムのリポジトリにPR作成
  • 当時の関係者でPRレビュー
  • 月に1度の発表の場でPRのレポートを発表
  • PRをマージして完了

ようやく対応が終わってからのインシデントの発表を億劫に感じるメンバーもいたため、日々の業務や運用フローにからめるなどて、もう少し発表者の負担を軽減できないかと運用を見直している最中です。

また、コストはかかるものの、ポストモーテムの場で共有することで自チームにも他チームにも学びがありサービスの向上につながると認識してもらうために、もっと体験を良くできる方法を模索しています。

失敗したこと:議論しやすい場を作り切れていないこと

参加人数が増えたせいか、以前よりも議論や考察の会話の量が減ってきています。

以前は、対応内容について意見の交換があったり、改善のアイデアをライトに言い合ったりする場だったのですが、人数が増えて一人あたりの発言の機会が少なくなったせいか、以前よりも議論の時間が少なくなりました。

また、以前は報告されるインシデントも大小様々で、中にはインシデント発生まで至っていなくとも、ヒヤリハットや運用の学びの出来事が発表されることもありました。

おそらく、議論のネタやアイデアを持ち込むことや、議論そのものに参加することで得られる良い体験や学びの実感が薄くなってきてしまっているのだと思います。

今は全チーム一緒になって開催しているため、一人ひとりの発言の機会を増やすために、報告後の議論をいくつかのグループにわけての実施や、チーム単位での実施も検討しました。ですが、自チームの事象についてそもそも当事者であるためすでに議論がなされていることもあり、他チームとの共有や意見交換も意義の一つであると思い、開催の変更には至っていません。

現在もポストモーテム自体は開催していますが、参加してくれるメンバーが良い体験と感じてもらえるように、議論を促したり質問が気軽にできたりするような場をつくれるように日々やり方や進行を模索しています。

まとめ

課題や失敗は色々ありましたが、総じて「情報を整理して、共通の文脈にあわせた上でチームに共有して、共通の認識をつくっていくこと」をもっと大事にして、「活動’の意義や価値を実感してもらうこと」を増やすべきであっと反省しています。言語化して伝えるだけではなく体験として実感してもらうことが一番伝わると思っています。

チームの設立時からジョインしていたり、同じ立場やロールで動くメンバーがいる場合には、共通言語や共通認識の基礎があるため、それらにずいぶん助けられていたのだと感じました。

複数のスモールチームに参画し、新しいことを始めて運用に取り入れて継続的に活動していくことは、土台づくりからとなるため、時間もかかりとても難しいです。

ですが、各チームの各メンバーがオーナーシップをもってサービス開発に取り組んでおり、良い活動となりそうなことについては積極的に取り入れる文化があることに大きく助けられました。そのおかげで新参者の私が新しいポジションでも活動を続けられています。

「良い活動となりそう」と感じてもらった上で、実際にサービスの価値や信頼性をあげられるように、今後もチームと一緒に議論しながら活動していこうと思います。

これから

現在は、IaCやCI/CDの改善、ログ収集と分析に取り組んでいます。

また今年になって、インフラおよびSRE業務にメンバーが一人増えました。

できることも増え、速度もあげられているのでとてもうれしいです。また何より、各チームと関わりはあったものの、チームとしては一人だったので、楽しさやモチベーションもあがりました。

ですが、まだまだ課題も取り組みたいこともたくさんあります。

フォースタートアップスでは一緒に取り組んでくれる方を募集しています。まだまだ未熟な部分は多いですが、興味を持っていただけましたらぜひご連絡ください。

GithubActions未経験者がcreateトリガーでブランチのフィルタ条件を追加してみた話

こんにちは、2022年4月にフォースタートアップスにジョインしたエンジニアの八巻(@hachimaki37)と申します。主に社内向けプロダクト「タレントエージェンシー支援システム(SFA/CRM)」のシステム開発を担当しております。

今回は初めてGithubActionsの改修チケットに関わることになり、途中ヒイヒイ言いながらも試行錯誤して解決に至った話について書いていければと思います。

本題に入る前に、まずは「GithubActionsとは?」と「GithubActionsの導入経緯について」簡単に述べていきたいと思います。

GithubActionsとは?

GithubActionsはGithubが2019年に出したCI/CDサービスです。

GitHub Actionsを使用すると、ワールドクラスのCI / CDですべてのソフトウェアワークフローを簡単に自動化できます。 GitHubから直接コードをビルド、テスト、デプロイでき、コードレビュー、ブランチ管理、問題のトリアージを希望どおりに機能させます。

引用元:https://github.co.jp/features/actions

当社の導入経緯について

弊社のユースケースですとGithubActionsの方にコストメリットがあることがわかったため「コスト削減」を目的にCircleCIからGithubActionsへ移行しました。

導入されたがこんな問題があった

当社の場合、pushトリガー + ブランチのフィルタ条件を用いて発火条件を制御しております。

サンプルコード

name: staging Build and Deploy

on:
  push:
    branches:
      - staging_axx**
      - staging_bxx**
      - staging_cxx**

※サンプルコードをベースに以下話を進めています

1. Github上からブランチを作成するとGithubActionsが発火しない

pushを発火条件としているため、Github上でのブランチ作成(=pushなくGithub上にブランチができあがる)がこの条件にヒットせず、発火されない

2. ブランチのpush順序をミスるとGithubActionsが発火されない

▶︎正しい順序

  1. ローカルPC上で、作業用ブランチをmasterから作成する(feature/hogehoge)
  2. ローカルPC上で、stagingブランチを作業用ブランチから作成(staging_axx/hogehoge)しpushする
  3. 作業用ブランチ(feature/hogehoge)をpushする

▶︎誤った順序

  1. ローカルPC上で、作業用ブランチをmasterから作成する(feature/hogehoge)
  2. 上記3を先に実行する(作業用ブランチ(feature/hogehoge)をpushする)
  3. 上記2を次に実行する(ローカルPC上で、stagingブランチを作業用ブランチから作成(staging_axx/hogehoge)しpushする)

当社では、github-flowに沿った運用を採用しております。誤った順序でpushした場合、pushトリガーの条件にヒットせず、GithubActionsが発火されない

上記の体験から非常に使い勝手が悪い!!そんな声が開発メンバーから上がっておりました。

どんな状態を目指したのか

シンプルに「必要な時に、適宜GithubActionsを発火させたい」ということです。

最初のアイディアは、pushトリガーのようにブランチフィルタを使えば簡単に実現できる!と思っていたのも束の間、実現にあたってこんな問題を抱えておりました。

The create event does not support branch filter and tag filter.つまり、pushトリガーのようなブランチのフィルタ条件が、createトリガーにはサポートされていなかったのです。悲しい...

調査と試したこと

ここからヒイヒイ言いながら試行錯誤の日々が始まりました。

  • createトリガーを追加したらどのような動作が走るのかまず試してみる
  • ん?createトリガーってブランチフィルターをそもそもサポートしてないんじゃ無理くないか?と思いながら、いい解決方法ないかなぁとググりまくる。あった!→https://github.com/orgs/community/discussions/26286
  • jobs自体に条件を追加してみる
  • stepsに条件を追加してみる
  • 良い記述方法を模索する
  • 条件を追加してもSyntax errorがたくさんでる..そもそもどうやって記述すればいいのだろうか

などなど、試行錯誤をしながら進めておりました。

苦労したこと

  • 適切な箇所(jobs, stepsなど)への条件追加を模索したこと、追加したはいいもののSyntax errorが多発するなど、最初は構文理解に苦労しました。「コードを追加してみてpush」「Github上からブランチをcreateする」を繰り返すことで、徐々に理解に繋がりました。
  • 全てのブランチでjobsが発火してしまう。当たり前ですが、createトリガーを追加すると全てのブランチを作成したタイミングでjobsが発火してしまうため、頭を悩ませました。ここは記事(https://github.com/orgs/community/discussions/26286)を参考にコードを書いてみて、特定のブランチ名だと「success」「skipped」になる方法で、解決に至りました。

実装したサンプルコード

createされたブランチが、以下のブランチ名にマッチする場合に発火するよう、条件を追加しました。

name: staging Build and Deploy

on:
  push:
    branches:
      - staging_axx**
      - staging_bxx**
      - staging_cxx**
  create:

jobs:
  build:
    name: Build and Push
    runs-on: ubuntu-latest
    timeout-minutes: 30
    environment: staging

    if:
      contains( github.ref_name, 'staging_axx/' ) ||
      contains( github.ref_name, 'staging_bxx/' ) ||
      contains( github.ref_name, 'staging_cxx' )

    env:
      省略

    steps:
    - name: Checkout
      uses: actions/checkout@v2

- name: hogehoge
      run: test1
- name: foofoo
      run: test2
- name: fugafuga
      run: test3

createされたブランチがマッチした場合

ブランチ名:staging_axx/fix-hogehoge

createトリガーが発火し、GithubActionsが実行される

createされたブランチがマッチしなかった場合

ブランチ名:staging_zxx/fix-hogehoge

createトリガーが発火せず、skipされる

あとがき

正直結構な時間を費やしてしまいチームにご迷惑をおかけしてしまいましたが、リリース後、チームの方からは嬉しい嬉しいフィードバックがありました!!!

今回記述した設定ですと、対象ブランチの数が増加するとコードが​​冗長になるので、リファクタリングができないか?と考えておりますが、問題自体は無事解消に至りました。

私自身エンジニア歴は3年ほどで、今まではサーバーサイドをメインに経験を積んできました。次のステップを考えた際にフルスタック(コーディング以外も含む)に動けるエンジニアへの成長を目指し、フォースタートアップスにジョインしました。

今回初めてGithubActions周りのチケットに携わりましたが、直近はVue.jsなどフロントエンドにも関わる機会が増え、嬉しい成長痛を日々感じ業務にあたっております。

なかなか経験のないチケット着手はワクワクと不安の感情が入り混じりますが、当社では初めてのチケットの場合「good first issue」を付け、初めてでも取っ掛かりやすい環境を構築しております。

まだまだやるべきこと、やりたいことがたくさんあります。ぜひ一緒に成長していきませんか? 当社は採用大募集中です。ご興味ありましたらぜひ一度カジュアルにお話できたらと思います。

採用ページはこちら。(冒頭のTwitterにDM頂いてもOKです!)