【技術書紹介】GitHub CI/CD実践ガイド

## 書籍情報
書籍名：GitHub CI/CD実践ガイド――持続可能なソフトウェア開発を支えるGitHub Actionsの設計と運用
著者名：野村　友規

https://gihyo.jp/book/2024/978-4-297-14173-8


## 本書を選んだ背景
TerraformによるAWSなどのクラウド上のリソース構築に興味があり、GitHub Actionsと連携させての構築の自動化をやってみたいと考えたため。

## 本書について
GitHub Actionsの基本的な使い方から、実際に設計や運用するにあたってのコツや注意点が記載されています。
ハンズオン形式で、実際に手を動かしながら学ぶことができます。

## 本書の内容
以下、個人的なメモを残します。
### 1章　ソフトウェア開発とGitHub
- GitHub CLIのセットアップと使い方、リポジトリの作成
    ```
    brew install gh
    gh --version
    gh auth login
    
    gh --help
    gh repo list
    gh pr create —fill-first —web
    gh repo create my-repo --public --clone --add-readme
    ```

### 2章　GitHub Actionsの基礎概念

- 基本的なワークフロー構文は以下の通り
    ```
    name: Hello  # ワークフロー
    on: push  # イベント
    jobs:  # ジョブ
      hello:  
        runs-on: ubuntu-latest  # ランナー
        steps:
          - run: echo "Hello, World"  # ステップ1
          - uses: actions/checkout@v4  # ステップ2
    ```

- ワークフローは指定したイベントでジョブを実行する。省略可能だが実行ログがわかりづらくなるので、常に記述する
- イベントはonキーへプッシュなどを指定する
- ジョブはワークフローの実行単位。jobsキー配下へ記述する。ジョブは複数定義でき、各ジョブを区別できるようにジョブIDを指定する
- ランナーはジョブの実行環境。runs-onキーへ記述する。GitHub Actionsはこの指定に基づいて、ジョブの実行環境をプロビジョニングする
- ステップはワークフローにおける処理の最小単位。stepsキーへ記述する。複数定義でき、処理は上から記述順に逐次実行される
　→runsキーではシェルコマンドが実行でき、usesキーではアクションを呼び出せる

- GitHub Actionsの実行
    ```
    git add .github/workflows/hello.yml 
    git commit -m "Add hello.yml"
    git push
    ```

- ブラウザにて実行ログを確認できる。CLI では以下で確認
    ```
    gh run watch
    gh run view
    ```
- エラーについて
    - ワークフローの構文エラー。例えば「runs-on」が「run-on」になってるとか
    - YAMLの構文エラー。インデントがおかしいとか
    - 実行時エラー。実行時に0以外の数値を返すとか（0を返したら成功）

- ワークフローの実行環境
    - ワークフローの再実行、キャンセル、無効化が可能

- ジョブの実行環境
    - ランナーには以下の2種類がある
        - Github-Hosted Runners
            - マネージド環境。latestバージョンのOSとさまざまなツールが インストールされる
            - ジョブごとに新しいランナーが作成され、終了すると破棄される。次回実行時にはクリーンな環境が提供される。この特性をエフェメラルと言い、ジョブの一貫性を高め環境依存の不具合が起こりにくい
        - Self-Hosted Runners
            - 利用者が実行環境を用意する。自由度が高いが運用の手間がかかる

- アクション
    - アクションはマーケットプレイスから探せる
    Verified  CreatorsはGitHubにより検証されたアカウントを意味し、アクション選択時の判断材料になる

- GitHub Actionsの課金モデル
    - パブリックリポジトリは無料、プライベートリポジトリは使用時間とストレージ使用量に応じた従量課金

### 3章　ワークフロー構文の基礎

- コンテキスト
    - 実行時の情報やジョブの実行結果などを保持するオブジェクト。${{github.actor}}のように記述する
        ```
        name :Contexts
        on:push
        jobs:
        	print:
          		runs-on:ubunts-latest
        		steps:
        			-run:echo ”${{github.actor}}”
        ```
- githubコンテキスト
    - ワークフロー実行時の情報やトリガーとなったイベントの情報を保持
    - github.eventプロパティのみオブジェクト型（ほとんどは文字列型）で、参照方法が異なる

- runnerコンテキスト
    - ジョブ実行時のランナー情報を保持

- 環境変数
    - envキーで定義。ワークフロー・イベント・ジョブの各レベルで定義可能。定義場所によって環境変数のスコープは変わる
    - 文字列だけではなく、コンテキストも指定できる。以下のように定義する
    
        ```
        env:
         BRANCH:msin
        ```
    - {{env.BRANCH}}のように記述して、envコンテキストを経由して参照することも可能
    - オーバーライド可能。スコープが狭い方が優先される
    - デフォルトの環境変数が用意されており、コンテキストのプロパティがほぼそのまま定義されている
    - コンテキストに特殊文字が含まれシェルコマンドの実行に影響を与える恐れがある。その対策として環境変数経由でコンテキストを渡してクォートする。中韓環境変数というテクニック。以下のように記述する
        - コンテキストはシェルコマンドへハードコートせず、環境変数を経由して渡す
        - 環境変数はすべてダブルクォーテーションで囲む

            ```
            name :Intermediate environment variables
            on:push
            jobs:
            	print:
              		runs-on:ubunts-latest
                	env:
            			ACTOR:${{github.actor}}
            		steps:
            			- run:echo ”${ACTOR}”
            ```

- Variables
    - 環境変数は単一のワークフローでのみ有効だが、Variablesは複数のワークフローで同じ値を使える。事前登録が必要、CLIでも登録可能
    - ${{vars.USERNAME}}のようにvarsコンテキストからアクセスする

- Secrets
    - パスワードなどの機密情報を取り扱うときに使用する。事前登録が必要、CLIでも登録可能
    - ${{secrets.PASSWORD}}のようにsecretsコンテキストからアクセスする
    - 機密情報が漏れる可能性があるため、Secretsはログ出力しない

        ```
        name :Secrets
        on:push
        jobs:
        	print:
          		runs-on:ubunts-latest
        		env:
        			PASSWORD:${{secrets.PASSWORD}}
        		steps:
        			- run: echo ”{{PASSWORD}}” #ログ出力はマスタされる
        			- run: echo ”{{PASSWORD:0:1}} ${{PASSWORD#?}}”　#ログ出力はマスタされない
        ```

- 式
    - コンテキスト参照のほかに、リテラルや演算子、関数もある。以下は関数の例
        - contains（） 文字列を含むならtrue
        - hashfiles（） ハッシュ生成

- 条件分岐
    - ifキーへ式を指定すると、ステップやジョブの条件分岐ができる
    - ifキーとセットでステータスチェック関数を使う。failure関数を使えば、失敗時のみ通知する処理などが実装できる

- ネーミング
    - ステップ名やワークフロー実行名は可読性を上げるために記述することが推奨される

- ステップ間のデータ共有
    - GITHUB_OUTPUT環境変数とGITHUB_ENV環境変数で可能。可読性の高い前者を利用する

- GitHub APIの実行
    - GitHub APIの利用には認証が必要だが、クレデンシャルとしてGITHUB_TOKENが用意されている。secretsから取得し、パーミッションで制御する。ソースコードのアクセスにはcontentsスコープが必要だが、パーミッションを定義しない場合、ソースコードの読み込みは暗黙的に許可される。明示的にパーミッションを記述した場合は暗黙的許可が無効化されるため注意

- スターターワークフロー
    - 便利なリファレンス機能が提供されている

### 4章　継続的インテグレーションの実践

- フィルタリング
    - pathsキーでイベントのフィルターが可能。複雑だったり一括のフィルタリングがしたいならGlobを利用する
    - アクティビティタイプはイベントの種類に応じた制御。typesキーで実現する

- セットアップアクション
    - 使用する言語のバージョンを指定する。リポジトリ内のバージョンファイルも使用可能で、これによりワークフローファイルが複数あってもバージョンを一箇所で管理できる

- 静的解析
    - コードをスキャンして問題を早期に検出できる。サードパーティツールとしてactionlintが使い勝手がよい
    ※常にタイムアウトは有効にすべき
    ※パイプエラーが拾うため、pipefailオプションの有効化が定石。またデフォルトシェルを指定するべき

- Concurrency
    - ワークフローの起動を制御する。 多重起動の抑制、古いワークフローの自動キャンセルを実現

- CIの黄金律
    - クリーンに保つ‥CIのエラーを放置しない
    - 高速に実行する‥理想は5分以内
    - ノイズを減らす‥気にしなくていいものは流す

- 自動テストの運用プラクティス
    - ユニットテストを中心にする

- 静的解析の運用プラクティス
    - 不要な警告は無視せず抑止する。新規の警告は増やさず、警告が出たなら理由を理解する

### 5章　運用しやすいワークフローの設計

- ロギング
    - デバッグログの出力が可能。デフォルトでは無効になっているため、再実行時に有効化する
    - Bashのトレーシングオプションを有効化すれば、実行したコマンドとその結果が出力される
    - ログのグループ化、手動マスクも可能

- レポーティング
    - アノテーションはジョブページへ任意のメッセージを出力できる。レベルはNotice、Warning、Errorのように設定するとよい
    - ジョブサマリーは凝った出力を実現する。マークダウンテキストを出力すると、ジョブページで綺麗に整形して表示される

- 複数ジョブの実行制御
    - ジョブはデフォルトで並列実行される。適切に分割するとワークフローの時間が短縮できる
    - 依存関係がある場合は並列ではなく逐次実行する
    - ジョブの実行結果は後続の別ジョブへ受け渡しできる

- マトリックス
    - 単一のジョブ定義で複数ジョブを実行できる。多次元マトリックスもあり

- Environment
    - 環境ごとに異なるデータを管理できる

- キャッシュ
    - ファイルのダウンロードを高速化できる。セットアップアクションを使えば簡単に扱える
    ※プラットフォームごとに異なるキャッシュを利用する
    ※依存関係を更新したときだけキャッシュも更新する

- アーティファクト
    - ワークフロー内で生成したファイル（ビルドしたバイナリファイルなど）をアーティファクトと呼ぶ。GitHubが管理するストレージへ一時的に保存可能。アップロードおよびダウンロードが可能。

### 6章　アクションによるモジュール化

- アクションの分類
    - アクションの実装は、Composite Actionが簡単。ワークフロー構文と似た記述で実装できるため
    - アクションにはメタデータファイルが必要。メインロジックや入出力インターフェイスを記述する
    - メタデータ構文はワークフロー構文と違いは以下
        - シェル指定を省略できない
        - 実行時に意図した値が取得できるかわからないため、github.eventは避ける（入力パラメータ経由で個別に渡す）
        - VariablesやSecretsにアクセスできない
        - パーミッションを定義できない

- アクションの設計プラクティス
    - 認知負荷低減のために名前と概要にこだわる
    - 切り出したスクリプトは環境変数を経由する
    - 値が暗黙的にやり取りされると、コードが追いづらく意図せず壊れるかもしれないため、明示的にやり取りする
    - ログはグループ化する

### 7章　クリーンなリポジトリの維持

- コードオーナーは個人またはチームで設定する
- クレデンシャルはバージョン管理しない。GitHubにはクレデンシャルの混入を検出・抑制する機能がある

- ドキュメント
    - READMEはリポジトリの全体像を伝え、LICENSEで権利関係を明確にする

### 8章　Dependabotによる依存関係バージョンアップ

- Dependabot
    - 依存関係のバージョンアップで活躍する。最新バージョンへの自動アップデート機能を提供する

### 9章　GitHub Releasesによるリリース自動化

- GitHub Releases
    - ソフトウェアの配信やリリースノート作成の自動化。ブラウザ、GitHub CLIから操作可能

- バージョニング
    - セマンティックバージョニングは「1.2.3」のように表記。メジャー、マイナー、パッチの順
    - Gitタグを利用するのもよい

- アナウンス
    - リリースノートに変更や追加、改善を記載する。似たドキュメントとしてはChangelogがある

- リリースノートの自動生成
    - プルリクエストに基づいて決定する。粒度は細かい方がよい。絞り込みはプルリクエストのラベルへカテゴリを設定することで実現する

- Gitタグの保護
    - ルールセットを利用する。これによりGitタグやブランチが保護できる

### 10章　GitHub Packagesによるパッケージ管理

- GitHub Packages
    - マネージドなパッケージレジストリサービス。いくつかの言語パッケージ、コンテナイメージをサポートする。パブリックなパッケージであれば無料だが、プライベートなパッケージはストレージ使用量とデータ転送量に応じた従量課金

- Container Registry
    - コンテナイメージを管理するパッケージレジストリ。アクセスにはGitHubのクレデンシャルが必要。ログイン後は一般的なdockerの操作と同じ

- GitHub Packagesの管理
    - パッケージとリポジトリは論理的に独立したリソースだが、リンクする仕組みが提供されている。リンクするとパーミッションが継承される
    - リポジトリとパッケージの自動リンク機能を使えば、パーミッションの自動継承が行われ管理が楽になる

### 11章　OpenID Connectによるセキュアなクラウド連携

- クラウドプロバイダのクレデンシャル
    - パスワードなどの静的クレデンシャルは使わない。一時クレデンシャルを使う

- OpenID Connect（OIDC）
    - アイデンティティ連携（複数の異なるドメインで認証結果を共有する）を実現するプロトコル
        - GitHub ActionsでOIDCを使うと静的クレデンシャルの管理が不要
        - クラウドプロバイダ認証時にアクセス元を細かく制限可能
    - OIDC TrustでGithubの生成したトークンを信頼し設定、Cloud Rolesで一時クレデンシャルのアクセス元とアクセス先を制御
    - 事故防止のために、プライベートリポジトリを利用し、認証パラメータをSecrets管理する

### 12章　コンテナオーケストレーションのデプロイメント

- コンテナビルドアクション
    - コンテナイメージのメタデータを生成しAmazon ECRにプッシュする

- コンテナデプロイアクション
    - タスク定義のimage部分をデプロイ対象のコンテナイメージに書き換え、新しいタスク定義でECSサービスを更新する

- 複数環境のデプロイにはEnvironmentsを利用する

### 13章　アクションのオープンソース化

- アクションの公開
    - GitHub Marketplaceで公開する場合、独立したパブリックリポジトリで管理する。また、メタデータファイルは1つだけ含め、リポジトリのルートディレクトリへ配置する

- アクションのテスト
    - Setup、Exercise、Verify、Teardownの4つのフェーズでテストする

- アクションのドキュメンテーション
    - READMEには概要や使い方のほかに、環境やパーミッションも記載する

- アクションの進化プロセス
    - 指針は「１つのことをうまくやる」。ユースケースの明確化、責務とインターフェイスを小さくする、利用者の満足度向上、戦略的な機能追加を心がける

### 14章　GitHub Actionsの高度な使い方

- Reusable Workflows
    - ワークフロー全体をカプセル化可能
    - workflow_callイベントで起動する
    - 入力パラメータ定義はinputsキーとsecretsキー
    - 出力値定義はoutputsキー
    - 出力は記述が多段になる
    - 入力パラメータの指定方法はwithキーとsecretsキー
    - 出力値参照はneedsキー

- 動的なワークフロー定義
    - fromJSON関数を使う。動的なマトリックス生成や文字列の変換が可能

- エラーハンドリング
    - ステータスチェック関数を使う。continue-on-errorキーを使うとエラーを無視して次の処理に進む

- コンテキストによるフロー制御
    - stepsコンテキストはステップの終了状態を保持、needsコンテキストは（依存している）ジョブの終了状態を保持。ステータスチェック関数の併用が必ず必要

### 15章　GitHub Actionsのセキュリティ

- セキュリティの設計原則
    - GitHub Actionsのセキュリティの設計原則は、アタックサーフェスの最小化、多層防御、最小権限の3つ

- GitHubのサービス特性
    - 寛容なデフォルト設定、リポジトリによるセキュリティ境界、コラボレーションによるアタックサーフェスの拡大

- 対策
    - リポジトリの保護、サードパーティアクションのセキュリティ、スクリプトインジェクションの無害化、最小権限のパーミッション、クレデンシャルの適切な運用、Forkプルリクエスト対策、OpenID Connectハードニング

### 16章　セキュリティのシフトレフト

- セキュリティ対策は後回しにせず、早めに取り組む（シフトレフト）。問題検出のためのツールやサービスはいくつかある
- 依存関係の脆弱性スキャン（Dependency graphなど）
- シークレットスキャン（Secretlintなど）。クレデンシャルのハードコードを検出
- アプリケーションのセキュリティ改善。Static Application Security Testingによるチェックとイメージスキャンなど
- IaCセキュリティ。静的抑制ツールの力を借りて問題を抑止

### 17章　GitHub Appsトークンによるクロスリポジトリアクセス

- クロスリポジトリアクセスには別途GitHubのクレデンシャルを払い出す。一個人が払い出したクレデンシャルの利用は避ける
- GitHub Appsトークンは最大で1時間有効。そのため漏えいしても影響は限定的

### 18章　継続的デリバリーの実践

- 組織パフォーマンス
    - ソフトウェアデリバリーパフォーマンスは、組織パフォーマンスと高い相関がある。スピードが速い組織ほど品質も高い

- バージョン管理戦略
    - ソフトウェアに関するものは全てバージョン管理する。またプルリクエストに使用するブランチは、少なくとも1日に1度はデフォルトブランチにマージする

- テスト戦略
    - 探索的テストで、人間が手動で未知の問題を探す
    - リリース後も本番環境でテストする

- リリース戦略
    - 何度も実行しリリースへの恐怖を克服する
    - チーム全員がロールバックに慣れておく。平時にテスト環境で練習する
    - カナリアリリース（ユーザトラフィックを少しずつ新しい環境に流す）によりデプロイとリリースを分離する

- データベースの変更管理
    - データベースコマンドはマイグレーションスクリプト経由で実行する
    - マイグレーションは安全を最優先し少しずつ進める
    - 影響する行数など、事前にマイグレーション時の影響を分析する

- IaCの変更管理
    - ドライランの結果を事前にレビューし、影響範囲を確認する
    - 本番環境へ適用するのは、デフォルトブランチへマージしたコードのみ
    - 構成ドリフト（コードと実行環境のズレ）が発生したら早急に対処する

- 疎結合なアーキテクチャ
    - チームが独立して自由にテストやデプロイが実行できる状態を実現する

- 運用を忘れない
    - 信頼性を高めユーザの期待に応える。また実装者自身が運用しユーザからフィードバックを得る

## 本書の感想
- ハンズオン形式で実際に手を動かしながら学べる点がありがたい
- 関連するツールやサービスも多く紹介されており、辞書的な使い方をするのが良いかも
- 本書は1章から6章が基礎編、7章から13章が実践編、14章以降が応用編という形で構成されていますが、実践編以降は踏み込んだ内容が多く、初学者には少し難しいように感じました