見出し画像

[報告] サイボウズでインターンしてきました

9月11日から15日までの5日間,サイボウズ株式会社でソフトウェアエンジニアをさせていただいておりました.

インターンについて

「Garoon CI/CD改善」コースに参加しておりました(コース詳細).

インターンは全コースフルリモートで開催され,それに対して2万円/日の給与が与えられます.

選考プロセスは 書類選考→面接 でした.書類選考ではCVを提出し,面接ではエンジニアの方とカジュアルに技術的な雑談をしました.人事の方がファシリテーターをしてくれていたので新鮮で,スムーズにミーティングを進められていた印象です.

Garoonについて

サイボウズ Garoonガルーン)は、幅広い世代が使いやすく、現場にも管理職にも浸透するグループウェアです。

https://garoon.cybozu.co.jp/

2002年から提供され,今では7000社以上で使われているということで,多様な機能と複数のバージョン(オンプレやクラウドなど)があり,コードが非常に複雑かつ巨大でした.そして,CI/CDのフローも複雑で巨大でした

応募した動機

去年,Microsoftでのインターン中にGitHub Acitonsを開発したのをきっかけにDevOpsに興味を持ちはじめました.CI/CDをいじれるインターンを探している時にこのコースを見つけました.

  • 大規模システムのCI/CD環境を見て改善したい

  • サイボウズのエンジニアの働き方を知る

やったこと

  1. AWSのECRトークンの期限切れ対策

  2. 失敗したテストの自動再実行

  3. 古いワークフローのキャンセル

  4. 小さなバグ修正

初日はオリエンテーションと環境構築,最終日は成果発表資料作成 & 発表会,ということで実装自体は3日間しかなかったので,かなりタイトなスケジュールでした.もう少しスケジュールに余裕があれば,CI/CDの性能改善をやってみたかったです.

インターンはもう一人の参加者である かとーはんと一緒にモブプロをしながら進めていきました.

GaroonのCI/CD

今回ターゲットとなったのはアーカイブ作成 & E2Eテストを行うワークフローです.

アーカイブ作成 & E2Eテストのワークフローアーキテクチャ図

たった一つのワークフローですが,しっかり大きくて嬉しくなっちゃいました.

Amazon ECRの認証トークンの期限切れ対策

起こっていたこと

E2Eテスト時に,ECRに保存されたE2Eテスト用イメージを取得するのですが,そのために以下の2つのプロセスを踏んでいます.

  1. ECRにログインして認証トークンを取得

  2. トークンを使用してイメージを取得

それぞれはもう少し大きな粒度のジョブに内包されています.前者はテスト準備,後者はテスト実施といったところでしょうか.ここでは1.と2.が別々のジョブにセパレートされているということがミソです.

E2Eテストは失敗します(突然).なので失敗したジョブを再実行するのですが,ここで問題が発生します.E2Eテストが失敗すると,1.を含むジョブ→成功,2.を含むジョブ→失敗,という状態になります.この状態で時間をおいて「Re-run failed job」をすると,2.自体が失敗しテスト自体が走らないことがありました.これは1.で取得した認証トークンには有効期限があるため,一定時間(デフォルトだと12時間)を超えると失効するということが原因でした.かといって「Re-run all jobs」をするにはワークフローは巨大すぎます.

認証トークンは、IAM プリンシパルからアクセス可能で 12 時間有効なAmazon ECR レジストリにアクセスするために使用されます。

プライベートレジストリの認証 - Amazon ECR

やったこと

トークン取得部分を切り出し,scheduleトリガーを使って定期的に実行されるアクションとしました.GitHub Actionsにはcronで定期実行するためのトリガーオプションがあります.

on:
  schedule:
    # * is a special character in YAML so you have to quote this string
    - cron:  '30 5,17 * * *'

取得したトークンはリポジトリのsecretsに保存するようにし,元ワークフローのトークン利用部分をsecretsを参照するように変更します.

これにより,時間が空いてしまったE2Eテストでも転けることなく再実行することができるようになりました.

工夫したこと

secretsの保存には権限が必要です.ワークフロー内での権限管理にはPATを使うこともありますが,より細かい粒度で一時的な権限を付与するためにGitHub Appsを使いました.ワークフロー内でアクセストークンを発行するのはtibdex/github-app-token(※1)というサードパーティアクションを使いました.セキュリティ上の観点から,動作が確認されるバージョンのソースコードを確認し,そのバージョンのハッシュで指定するというプロセスを経ました.

GitHub AppsでGitHub Actionsの権限管理をするための知見は,DeveloperIOさんの記事が非常に参考になります.

※1:最近,公式でアクションが出たということで自分で試してみました↓

失敗したテストの自動再実行

起こっていたこと

上述の通り,E2Eテストは失敗します.正しいコードなのにテストに落ちることが多々ありますが,これまで失敗したジョブを手動で再実行していました.しかし,気付かれず放置されている場合もあったり,それによってコード内のバグの発見が遅れる危険性がありました.

やったこと

E2Eテストの再実行を自動化しました.これにより,何度再実行しても失敗する(コードが良くない)テストに集中できるようになります.

name: Rerun faild workflow
on:
  # <worklfow name that may fail>が完了したときにキックされる
  workflow_run:
    workflows: [<worklfow name that may fail>]
    types: [completed]
jobs:
  rerun-workflow:
    runs-on: ubuntu-latest
    # <worklfow name that may fail>が失敗していて、かつ、再実行回数が3回未満のときに実行される
    if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.run_attempt < 3 } }}
    steps:
      # GitHub CLIのためにGitHub Appsのトークンを生成する
      - name: Generate GitHub Apps token
        id: generate-token
        uses: tibdex/github-app-token@v2
        with:
          app_id: ${{ secrets.APP_ID }}
          private_key: ${{ secrets.PRIVATE_KEY }}
      # GitHub CLIを使ってワークフローを再実行する
      - name: Rerun workflow
        env:
          GH_HOST: github.example.com
          GH_ENTERPRISE_TOKEN: ${{ steps.generate-token.outputs.token }}
        run: gh run rerun ${{ github.event.workflow_run.id }} --failed

工夫したこと

本当に良くないコードがあった際に無限ループしちゃうと良くないので,run_attemptプロパティを使って実行回数を制限しました.また今回もGitHub CLIを用いたワークフロー再実行のためにGitHub Appsを用いた権限管理を行なっています.

得た知見

事前検証の際にいくつかユニークな知見を得ることができました.

  • ワークフローは自分自身を再実行することはできない

失敗するかもしれないステップに「continue-on-error: true」を設置しておくことで,失敗しても次のステップを実行してくれます.

この場合,ワークフローの成否としてのコンテキスト github.conclusion は "success" になります.
一方で,ステップ単位での成否判定は steps.<step's id>.outcome で確認することができます.

なので失敗するかもしれないテストステップの次に,失敗を検知してワークフローをセルフで再実行しようとしたところ,GitHubくんが「ファイルが壊れてるかも」と言ってきました.ちゃんと再実行回数制限はつけましたが問答無用でダメのようです.

セルフ再実行を試みた際のエラー
# こういうことがやりたかったというコード例
name: Try Self Rerun
on:
  workflow_dispatch:
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Generate GitHub Apps token
        id: generate-github-app-token
        uses: tibdex/github-app-token@v2
        with:
          app_id: ${{ secrets.GH_APP_ID }}
          private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
      # 失敗するテストを実行
      - name: Failure Test
        id: failure
        continue-on-error: true
        run: exit 1
      # 失敗したら3回までリトライ(上手くいかない)
      - name: rerun-workflow
        if: ${{ steps.failure.outcome == 'failure' && github.run_attempt < 3 }}
        env:
          GH_TOKEN: ${{ steps.generate-github-app-token.outputs.token }}
        run: gh run rerun ${{ github.run_id }} --failed
  • reusable workflowの再実行はできない

冒頭で巨大なワークフロー図を見せたと思いますが,その中でテスト自体はreusable workflowとして書かれており,それ自体をターゲットにした再実行を試みたのですが,GitHubに無視されました(特にエラーも出ず,只々再実行しない).

# こういうワークフローを再実行することはできない
name: Reusable workflow
on:
  workflow_call:
jobs:
  failed-test:
    runs-on: ubuntu-latest
    steps:
      - run: exit 1

# --------------------------------------
# 再実行をさせるワークフロー
name: Rerun Failure Test
on:
  workflow_run:
    workflows: [Reusable workflow] # reusable workflowを指定
    types:
      - completed


後から考えてみればどちらも当たり前で,GitHub CLIによる再実行にはワークフローを対象にとるので,どれだけロジックを組もうが「全部終わるまで正座して待て」ということです.しかし,サンドボックスを立てて検証しながら「あぁでもないこうでもない」してる時間はとても楽しく,ギリギリ役に立ちそうなテクニックが身についたのでハッピーです.

古いワークフローのキャンセル

起こっていたこと

無駄に動いているCIにより,待ち状態が発生してしまっていました.それは例えば,連続したcommit&pushやブランチのrebaseなどが発生原因です.大きなワークフローなので待機時間がバカになりません.

やったこと

concurrency を使って,動作中の同一ブランチかつ同一のワークフローを検知し,古いワークフローをキャンセルするようにしました.

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

group が同一のワークフローは同時に実行されなくなり,cancel-in-progresstrue に設定することで古いワークフローはキャンセルされます.上記の例では,github.ref によってブランチが識別されるため,「同一ブランチかつ同一のワークフロー」を検知してキャンセルするようにできます.

group名をワークフロー毎にユニークにするために github.workflow を使う例が多いですが(公式含め),reusable workflowにおける github.workflow は親ワークフロー名になってしまうので,親子でconcurrencyがセットされている場合,親子が同一のgroupとなり無限ループにならないよう注意が必要です.

「そうはならんやろ」→「なっとるやろがい」

また,特定のブランチではこのconcurrencyを効かせたくないという場合は,github.ref の代わりに github.shagithub.run_id などを利用すればスルーできます.

# mainブランチだけ,concurrencyを聴かせないようにする例
${{ github.ref == 'refs/heads/main' && github.sha || github.ref }}

小さなバグ修正

GaroonのPHPバージョンが正しいかを判定しているワークフローが,ある条件の時にエラーを出していました.PRのラベル依存のエラーだったので,ラベルの条件分岐を書いてエラーが出ないよう修正しました.

まとめ & 感想

GitHub Actionsのスキルアップ

個人開発だとreusable workflow を使うレベルのワークフローを構築しないので,細かい挙動について手を動かしつつ学べたのは良い経験になりました.

YAML以外書いてない,そんなインターンも珍しくて良いですね.

メンターの手厚いサポート

インターン中はずっとメンターさん(複数)がzoomを繋いでくれており,気になる点や詰まりそうになった時に的確なアドバイスをくれました.なんだかんだで同期的コミュニケーションいいなと感じました.

美味しすぎる食費補助

毎日1500円のランチ代補助が出たので,Uber Eatsやコンビニで豪遊しました.加えて最終日の懇親会用食事代にはなんと2500円,お酒とデザートをたくさん買いました.

人の金で食う飯がいっちゃんうまい

Garoonやkintoneを知る

もちろん社内でのグループウェアはGaroonやkintoneが採用されており,最初は慣れなかったんですが,3日目くらいには慣れてきて,統合的なグループウェアだからこその機能性やデザインが理解でき,僕が普段使っているGoogleやMicrosoftのグループウェアとのターゲット層の違いをなんとなく把握できました.

全体を通して,歴史のある大規模システムのCI/CD開発に関われたことを嬉しく思うとともに,メンターや人事の方々の手厚いサポートに感謝しています.

DevXもっと頑張ります.対戦ありがとうございました.

完走証をいただきました(掲載許可有り)


この記事が気に入ったらサポートをしてみませんか?