元ラーメン屋店長プログラマのObjective-ひろC(@hirothings)です🍜
GitHub Actionsで個人アプリのデプロイを自動化したのでその手順を書きます。
目次
- GitHub Actionsとは?
- なぜGitHub Actionsをデプロイ環境に選んだか?
- 実装の流れ
GitHub Actionsとは?
「GitHub上で動作するサーバレス実行環境」で、GitHub関連のイベントをトリガーにCI/CD様々な処理を実行できます。
中の人がGitHubの概要について説明しているわかりやすい記事があるので、それを読むのがオススメです。
以下、抜粋。
- Actionの実体はDockerコンテナ
- Actionを組み合わせて独自のワークフローを構築できる
- コンテナ実行環境がコンパクトで重い処理の実行には向いていない
特徴
GitHub Marketplaceに、actionがたくさんあり、自分のワークフローの一部に使用可能です。(詳しくはここ)
GitHubの各種操作に対するイベントのサポートが他CIより手厚く、様々なトリガーをもとに処理を実行できます。
- PR
- assigned
- opened
- closed .. etc
- push
- release
- project
- cron
..etc(詳しくはここ)
なぜGitHub Actionsをデプロイ環境に選んだか?
BitriseもCircle CIもiOSの個人開発で利用しようとすると有料プランの利用が必須になってきます。
- Bitrise
- ビルド時間の制約が10min以内
- Circle CI
- freeプランにmacOSの実行環境がない
GitHub ActionsはOSSだと利用が無料。Privateリポジトリは従量課金制ですが、1か月あたり2,000分(Proなら3,000分)まで無料という緩めの制約で、デプロイに利用するくらいであれば無料枠で利用できます。
そのため個人アプリ開発者のお財布事情に優しく、ヘビーユースしなければ無料でCI/CD実行環境が手に入ります
追記: macOSで処理を実行する場合、実行可能時間が10倍の速度で消費されます。つまり最短で200分/月となります。(公式ドキュメントより)
サポートされている環境
iOSのビルド周りに必要な、主要なツールはプリインストールされています。
Xcodeに関してはBitriseは19/12/28現在、12/3に出た11.3がサポートされているので、アプリに特化したCIサービスに比べると少しサポート遅めです。
- macOS 10.15(Catalina)
- Xcode11.2.1(19/12/28現在)
- fastlane
- iOS simulator (19/12/28現在: 13系のみ)
- Bundler
- Carthage
- CocoaPods
- Homebrew
GitHub Actionsにインストールされているソフトウェアは公式ページで確認できます。
GitHub Actionsの構成
ディレクトリ構造
リポジトリの.github/workflows/
をrootに複数のワークフロー(yamlで定義)がぶら下がる階層構造になります。
|-- hello-world (repository)
| |__ .github
| └── workflows
| └── my-first-workflow.yml
| └── actions
| |__ hello-world-action
| └── action.yml
ワークフロー内の構造
{some-workflow}.yml
の中に最低1つのjobがあり、複数のstepがぶら下がる構成になります。
公式ドキュメントより
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Run a one-line script
run: echo Hello, world!
- name: Run a multi-line script
run: |
echo Add other actions to build,
echo test, and deploy your project.
jobは並列処理も直列処理も実行可能(詳しくはここ)
Hello Worldまで
Actionsタブをクリックして、右上のSet up workflow yourself
ボタンをクリックすると、最初のworkflowの設定画面になります。
ワークフロー名(yamlのファイル名)を指定。(公式の例に合わせてハイフン区切りで命名)
そのままファイルをGitHub上で編集することもできます。(補完なども効く専用のエディタ)
このまま、コミットすればHello Worldまで実行できます。
デプロイ自動化の実装の方針
GitHub ActionsでiOSのビルド環境を構築するには、fastlaneを使うのがオススメと中の人が回答しているのでfastlaneで実装します。
実装
ReleaseブランチにPRがマージされたら、デプロイのActionを実行する例は下記です。(yaml)
記法については公式ドキュメントを読んでください。
上から読んでいけば何となく何をやっているか分かると思います。最後のstepでfastlaneを実行して実際のデプロイ処理を行っています。分かりづらい箇所を補足します。
name: Deploy to AppStore Connect
on:
push:
branches:
- release
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v1 # 1: MarketplaceのActionの利用
- name: Select Xcode
run: sudo xcode-select -s '/Applications/Xcode_11.2.1.app/Contents/Developer'
# 2: キャッシュを使う
- uses: actions/cache@v1
with:
path: Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: Pod Install
if: steps.cache-cocoapods.outputs.cache-hit != 'true'
run: pod install
# 4: fastlaneで必要なため、SSHキーのセットアップをする
- name: Setup SSH Keys and known_hosts for fastlane match
# Copied from https://github.com/maddox/actions/blob/master/ssh/entrypoint.sh
run: |
SSH_PATH="$HOME/.ssh"
mkdir -p "$SSH_PATH"
touch "$SSH_PATH/known_hosts"
echo "$PRIVATE_KEY" > "$SSH_PATH/id_rsa"
chmod 700 "$SSH_PATH"
ssh-keyscan github.com >> ~/.ssh/known_hosts
chmod 600 "$SSH_PATH/known_hosts"
chmod 600 "$SSH_PATH/id_rsa"
eval $(ssh-agent)
ssh-add "$SSH_PATH/id_rsa"
env:
# 3: 秘匿情報の受け渡し
PRIVATE_KEY: ${{ secrets.GITHUB_ACTIONS_SSH_PRIVATE_KEY }}
- name: Run Fastlane - Deploy to AppStore Connect
run: fastlane appstore_release
env:
TEAM_ID: ${{ secrets.TEAM_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
補足
1: MarketplaceのActionの利用
リポジトリのCheckoutなど主要な処理はGitHub謹製のActionが用意されているので、それを使います。
marketplaceで用意されているworkflowを使うときは、 {owner}/{repo}@{ref}
or {owner}/{repo}/{path}@{ref}.
の記法で参照します。 (詳しくはここ)
steps:
- uses: actions/checkout@v1
2: キャッシュの利用
cocoapodsやcarthageのキャッシュが可能なactionを公式が提供しています。
if
キーワードを組み合わせて、キャッシュがない場合のみインストールを実行するよう制御することが可能です。
- uses: actions/cache@v1
with:
path: Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: Pod Install
if: steps.cache-cocoapods.outputs.cache-hit != 'true'
run: pod install
3: 秘匿情報の受け渡し
秘匿情報は、リポジトリのsettings/Secrets
で指定でき、内部で暗号化され、アクションでのみ使用できます。
ワークフローがforkedリポジトリからトリガーされた場合、GITHUB_TOKEN以外の情報は渡されません。(詳しくはここ)
env:
PRIVATE_KEY: ${{ secrets.GITHUB_ACTIONS_SSH_PRIVATE_KEY }}
4: fastlaneで必要なため、SSHキーのセットアップをする
自分のケースだと、fastlaneのmatch
を使用しているため、SSHの設定が必要でした。
fastlaneのstepより先のstepでセットする必要があります。
2019.12現在、GitHub Actions公式のSSH設定関連のactionがなく、野良actionはあるのですが、悪用される可能性が0ではないので、scriptを書いて対応しています(参考)
秘密鍵の登録自体は、settings/keys
画面でDeploy Key登録しました。(Read権限のみ)
参考: Circle CI で Github に write access 可能な Deploy key を設定する
デプロイの実行
デプロイの実行自体は、stepの最後でfastlaneのlaneを叩くことで実行しています。
TEAM_ID
などfastlaneに必要な秘匿情報はenv
or with
キーワードを使って環境変数経由で渡します。
- name: Run Fastlane - Deploy to AppStore Connect
run: fastlane appstore_release
env:
TEAM_ID: ${{ secrets.TEAM_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
fastlaneの記事ではないので参考程度ですが、こんなlaneを実行してます。
fastfile
default_platform(:ios)
platform :ios do
desc "Push a new release build to the App Store"
lane :appstore_release do
begin
bump_build_version_with_commit
build_to_release
deliver
post_message_to_slack(message: "デプロイしたゴねー")
rescue => exception
post_errormessage_to_slack(message: "デプロイに失敗したゴねー", exception: exception)
raise exception
end
end
# private
private_lane :build_to_release do
create_keychain_for_CI if is_ci
match(
type: "appstore",
readonly: is_ci,
keychain_name: "KEYCHAIN_NAME",
keychain_password: ENV["MATCH_PASSWORD"],
)
disable_automatic_code_signing(
use_automatic_signing: false,
targets: ["PROJECT_NAME"],
code_sign_identity: "iPhone Distribution",
team_id: ENV["TEAM_ID"],
profile_name: "PROFILE_NAME"
)
gym(
workspace: "PROJECT_NAME.xcworkspace",
configuration: "Release",
scheme: "SOME_SCHEME",
export_method: "app-store",
)
end
private_lane :bump_build_version_with_commit do
increment_build_number(
xcodeproj: "PROJECT_NAME.xcodeproj"
)
git_commit(path: ".", message: "Version Bump")
end
private_lane :create_keychain_for_CI do
create_keychain(
name: "KEYCHAIN_NAME",
password: ENV["MATCH_PASSWORD"],
default_keychain: true,
unlock: true,
timeout: 3600,
lock_when_sleeps: false
)
end
def post_message_to_slack(message:)
webhook_url = "webhookのURL"
slack(
message: message,
success: true,
slack_url: webhook_url,
default_payloads: [:lane, :last_git_commit],
attachment_properties: {
fields: [
{
title: "Workflow",
value: ENV["GITHUB_WORKFLOW"]
},
{
title: "URL",
value: "https://github.com/hirothings/{Repository名}/commit/" + ENV["GITHUB_SHA"] + "/checks"
},
{
title: "AppStore Connect",
value: "AppStore ConnectのURL"
}
]
},
)
end
def post_errormessage_to_slack(message:, exception:)
webhook_url = "webhookのURL"
slack(
message: message,
success: false,
slack_url: webhook_url,
default_payloads: [:lane, :last_git_commit],
attachment_properties: {
fields: [
{
title: "Error message",
value: exception
},
{
title: "Workflow",
value: ENV["GITHUB_WORKFLOW"]
},
{
title: "URL",
value: "https://github.com/hirothings/{Repository名}/commit/" + ENV["GITHUB_SHA"] + "/checks"
}
]
},
)
end
end
実行!!
slackへの投稿もfastlaneで行っています。
ワークフロー名やCommit SHAは、GitHub Actionsのデフォルトの環境変数として利用可能です。
(デフォルトで利用できる環境変数について詳しくはここ)
実行したActionのURLは下記のパターンで生成されるので、
https://github.com/{ユーザー or 組織}/{リポジトリ}/commit/{Commit SHA}/checks
GitHub Actionsのデフォルトの環境変数GITHUB_SHA
を使えば、slackなど実行結果にリンクを貼ることが可能です。
無料枠の制限を超えそうになると?
Actionの実装でデバッグを繰り返したところ、1ヶ月の無料枠の制限を超えそうになってしまいました。無料枠の75%を超えたタイミングでGitHubからメールが来ました。
支出の制限の変更はユーザーだとhttps://github.com/settings/billing から指定できるので必要に応じて設定しましょう。詳しくはここ
参考記事
公式ドキュメント
https://help.github.com/en/actions
Github actions + Fastlane + iOS = ❤
https://medium.com/well-red/github-actions-fastlane-ios-1f6d43cce726
Github Actions で Xcode プロジェクトをビルドしてみる
https://qiita.com/koogawa/items/ddf86b56d6b82ae11473