All Articles

JenkinsからGitHub Actionsへの移行をキメた

社のCI/CDをJenkinsからGitHub Actionsに移行しました。公式ドキュメント読み倒してたくさんのymlを書いたので、tipsでも残して置きます。

環境

  • レポジトリは15くらい
  • デプロイ環境は、Amazon ECSとAWS Elastic Beanstalk
  • アプリケーションは全部Java / Spring Boot(Gradle)

移行の背景

2019年までに作ったアプリケーションのデプロイ・リリース作業はJenkinsで行なっていました。2020年に入ってコンテナ化が進み、AWS CodeBuild・AWS CodeDeployを使ってデプロイするようになったり、一部ではGitHub Actionsを使ってデプロイするようになったりとデプロイ・リリース方法が多様化していきました。
JenkinsはEC2インスタンス立てて、そこにインストールしていましたが長年メンテナンスされてなかったし、ジョブの作り上デプロイ完了待ちが発生していました。

移行理由をまとめると、次の通りです。

  1. デプロイ・リリース方法が多様化されている上にちゃんとドキュメントがなく、全環境の手順把握してる人もいないため、使うツールや手順を統一したい
  2. 数年Jenkinsのメンテナンスされてこなかったし、これからもメンテナンスしたくない
  3. ジョブの作り上、ビルド・デプロイに時間がかかるため改善したい

既存で使われていたのが、AWS CodeBuild・AWS CodeDeployとGitHub Actionsだったのでこの2択でした。前者だと、設定ファイル結構用意しないといけないしデプロイ作業が手間そうだったので、後者に決めました。

移行作業

主に作ったワークフローは次の通りです。

  1. ビルド用ワークフロー(PR作成・更新をトリガ)
  2. テスト環境にデプロイするワークフロー(手動実行)
  3. 本番環境にデプロイするワークフロー(手動実行)
  4. テスト環境に日次デプロイするワークフロー(スケジューリングトリガ)

公式ドキュメントを読めばできることしかやってないので、実際のワークフローの中身については省略します。

利用した公式アクション

工夫点

実行ログの永続化

デプロイログを一定期間残したかったので、独自でバッチを作りました。今はプライベートレポジトリの実行ログ保存期間は最大400日になっていますが、当時は最大90日だった(気がする)のでログを永続化する方法を考えました。

GitHub Actions側の後処理も含めて全ジョブ終了後にログが保存されるため、ワークフローの中にログを保存するジョブを入れることができませんでした。
そこで、1. GitHub APIを叩いてログファイルを取得2. 1で取得したZIPファイルをS3にアップロード を行うバッチを作って、GitHub Actionsのscheduleトリガを使って日次で動かすようにしました。

複数レポジトリにワークフローを置くのは管理コストが高くなるので、それ用にレポジトリを立てるようにしました。これは、ログアップロードバッチのレポジトリのログアップロード用ワークフローです。

name: Upload CI Log
on:
schedule:
- cron: '0 1 * * *'
jobs:
upload:
name: CI Log uplaad
runs-on: ubuntu-latest
strategy:
matrix:
repository:
- repo-a
- repo-b
- repo-c
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Cache modules
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Upload
env:
AWS_REGION: ap-northeast-1
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# GitHub APIを叩くための個人アクセストークン
DOWNLOAD_LOGS_ACCESS_TOKEN: ${{ secrets.DOWNLOAD_LOGS_ACCESS_TOKEN }}
run: |
./gradlew -x test build
# 引数はOrganization名/Repository名
java -jar ./build/libs/upload-ci-logs.jar Hoge/${{ matrix.repository }}
view raw upload.yml hosted with ❤ by GitHub

EB CLIのインストール

AWS CLIはデフォルトで、 インストールされている のですが、EB CLIはインストールが必要です。
このように書けば、20秒ほどでインストールできます。

steps:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install awsebcli
run: pip install -U awsebcli
view raw deploy.yml hosted with ❤ by GitHub

action用プライベートレポジトリ

GitHub Actionsでは、 独自アクション を作成することができます。複雑な処理をしたいときに使うのがよいかと思います。
独自アクションの使い方は次の通りで、action用のプライベートレポジトリを作るには工夫が必要でした。

  • 公開アクションは uses: Organization名/Repository名 で指定
  • 非公開アクションは実行するレポジトリ内に独自アクションを置き、 uses: ./アクション配置パス で指定

全レポジトリでSlackにデプロイ通知する必要がありました。複雑な処理だったので「独自アクションにしたい!」となったのですが各レポジトリに置きたくはなかったので、独自アクション×別レポジトリのチェックアウトでaction用プライベートレポジトリっぽいことを実現しました。

  1. action用プライベートレポジトリの用意
  2. アクションを作る(複数作ることを考えて、独自アクションごとにディレクトリ分けるのがおすすめ)
  3. アクションを実行したいレポジトリで、1で作成したレポジトリをチェックアウト
  4. 3でチェックアウトした独自アクションを実行

これは、アクションを利用するレポジトリのワークフローです。

steps:
- name: Checkout actions
uses: actions/checkout@v2
with:
# Hoge/actionsレポジトリにnotify-slackという名前のディレクトリを切って独自アクションを準備
repository: Hoge/actions
# 外部のプライベートレポジトリをチェックアウトするときは個人トークンが必要
token: ${{ secrets.TOKEN }}
# 配置するパス
path: actions
- name: Notification
uses: ./actions/notify-slack
with:
message: "Test notificatin"
view raw custom-action.yml hosted with ❤ by GitHub

入力値チェック

手動実行時の入力値は、プルダウンが使えないためワークフロー内で入力値チェックを行うようにした。

これは、パラメータ environment の入力値がtest1でもtest2でもない時に失敗させる例です。

steps:
- name: Check
if: ${{ github.event.inputs.environment != 'test1' && github.event.inputs.environment != 'test2'}}
run: |
echo "::error ${{ github.event.inputs.environment }} is invalid. environment must be 'test1' or 'test2'."
exit 1
view raw deploy.yml hosted with ❤ by GitHub

依存関係やビルドによる成果物をキャッシュする

公式の actions/cache を使えば簡単に実現できます。

jobやstep単位でタイムアウト時間を設定する

jobのタイムアウト時間はデフォルトで360分なので設定しないと長時間実行し続けて無料枠をすぐ消費してしまいます。
timeout-minutes: 時間(分) で指定します。 公式ドキュメント