TECHSTEP

ITインフラ関連の記事を公開してます。

『入門 継続的デリバリー』の感想: CI/CDの概念から実戦まで含めた良書でした

今回はオライリー入門 継続的デリバリー を読んだので、その感想です。本書は2022年に出版されたManning社のGrokking Continuous Delivery を和訳したものですが、とても面白く読ませていただいたので取り上げました。今回も読んでいてよかったと思うポイントを3つ、プラス読書中のメモを載せています。

www.oreilly.co.jp

CI/CDの入門から実戦まで幅広くカバーしている

本書は大きく4つの部と13の章に分かれており、第1部では継続的デリバリー及びその周辺の基本的な用語や考え方、第2部ではContinuous Integrationの実戦的な内容、第3部はContinuous Deliveryの実戦的な内容、第4部はContinuous Deliveryの始め方やデザインといったさらに発展的な内容を扱っています。

例えば第2部のCIはバージョン管理からテストまで、第3部のCDはツールを使ったデプロイやその後の改善など、実際にCI/CDを進めるうえで必要なものが一通り学べるかと思います。本書の序文にあり、書籍の帯にもなった一文(「この本が、すべてのソフトウェアチームでオンボーディングにおける必読書になることを願う」)も、決して誇大広告ではないと読後に感じました。

なお本書ではGitHub Actionsを題材に一部コードを紹介していますが、GitHub Actionsに依存した内容にはなっていないので、幅広いツールに適用できるものだと思います。

各章で架空の企業を例に分かりやすく説明してくれる

本書では架空の企業を題材に、CI/CDの各要素がなぜ重要か、どう改善するのが良いかを説明してくれます。

例えば第3章ではバージョン管理を紹介しますが、あるスタートアップ企業の創業者である2人のエンジニアが開発をする中で、チェックインする前にテストするのを忘れたことが原因でmainブランチ上のコードが壊れる過程を紹介し、「バージョン管理をリリース可能な状態にする」というCIの原則をどうやって保つか、といった内容が記載されています。

また第3部ではCDを紹介する中で、開発からリリースまでの時間がかかりすぎるという課題に対応するため、DORAメトリクスによるプロジェクトの可視化を行い、いかにしてそれを改善するか、という過程が描かれています。

個人的な印象ですが、CI/CDを学んでいると、抽象的な概念が多く登場することもあり、その考えをどう使うのか、なぜそれが重要なのか、なかなかイメージするのが難しいと感じる場面に多く出くわします。CI/CDについては一通り理解していたつもりだったのですが、具体的な過程を読むことで、それぞれの要素を具体的にどう設定するか、どう改善に生かすか、など、自分の理解が足りない部分もたくさん見えてきました。そのため本書ではこういった課題に対して有効な書籍であると感じます。

Linterについて1章使って詳細に説明してくれる

本書では第4章でLintについて扱っています。Lintはソースコードに対する構文チェックなどを実行する静的解析の1つで、導入のしやすさや短時間で処理が済むことなどから、多くのプロジェクトで利用していると思います。私も個人の学習からプロジェクトまで様々な場面で導入・利用していますが、一方でLinterを導入したはいいものの、そこで得られた結果をどう生かすか悩むことも多いです。

例えば、とりあえずLinterをCI/CDに組み込んで見たけど、大量のエラーが検知されてパイプラインが失敗、いったんはLinterによる検知を無視してパイプラインを続行するようにして、そのまま忘れてしまった、ということもありました。

本書ではレガシーなプロジェクトを題材に、Linterを導入するだけでなく、そこで得られた結果をどうプロジェクトに生かすかも紹介されています。例えばLinter導入直後は数万もの指摘事項がある中で、Lintで得られる問題には大きく3種類あること(バグ、エラー、スタイル)、特に影響のあるバグから対応するほうがよいこと、などが紹介されています。

Linterについて、導入から活用まで詳細に扱っている書籍は初めて遭遇したため、本書で初めて得られる情報も多かったです。個人的にはこの章を読むためだけでも購入する価値はあると思います。

読書時のメモ

以下は読書時のメモです。

1部: 継続的デリバリーとは

1章: 「入門 継続的デリバリー」へようこそ

  • continuous deliveryの定義(本書)
    • プロフェッショナルな品質のソフトウェアを書く複数のソフトウェアエンジニアが、思い通りのソフトウェアを作成できるようにするために必要なプロセスの集合体
  • continuous deliveryの定義(CDF)
    • ソフトウェア開発において、チームがソフトウェアの変更を安全、迅速、かつ持続的にユーザーにリリースする手法であり、以下2つを実践している。
      • いつでも変更がリリース可能であることを立証している
      • リリースプロセスを自動化している
  • integration: コードの変更を既存のソフトウェアにインテグレートする
    • ソフトウェアインテグレーションとは、複数人によって変更したコードを組み合わせて、そのコードが意図したとおりに動くかどうかを検証する行為です。
  • continuous integrationの定義(本書)
    • 継続的インテグレーションとは、チェックインの際に各変更を検証したうえで、コードの変更を高い頻度で結合していくプロセスのことです。
  • deliveryする対象
    • ライブラリ / バイナリ / コンフィグ / イメージ / サービス(アプリケーション)
  • delivery: ビルド・リリース・デプロイのうちの一つ、またはすべてを指す
    • デプロイ: ソフトウェアを実行したい場所にコピーし、実行状態にすること
    • リリース: ユーザーがソフトウェアを利用可能になること
  • continuous deployment: コミットごとにユーザーへ自動的にリリースされます

2章: パイプラインの基本

  • タスク: 実行する個々の作業のこと、関数のようなもの
  • パイプライン: コードへのエントリポイントのようなもの、全ての関数(タスク)を適切なタイミングで適切な順序で呼び出す
  • CDパイプラインのタスク
    • lint / test / build / publish / deploy
    • CI: lint / testのみ、検証のためのもの
  • 原則: パイプラインが壊れているときは変更をプッシュしない
    • CDシステム自体で禁止する、もしくは壊れていることを通知する(7章)

2部: 常にデリバリー可能な状態に保つ

3章: バージョン管理は継続的デリバリーの成功に不可欠

  • 資金を得た直後のスタートアップ企業の例
  • バージョン管理: プレーンテキストの変更を追跡するためのソフトウェア
    • 全てを保存する中心となる場所。リポジトリ
    • 全ての変更の履歴。変更ごとに新しい一意なバージョンを作成
  • continuous deliveryを行うにはバージョン管理が必要
    • CDにはCIが必要、CIには「変更を結合する方法」「変更を保存する場所」が必要であり、それがバージョン管理である
  • バージョン管理をリリース可能な状態に保つ
    • 例: mainブランチにバグを含むコードをコミットし、mainブランチが壊れてしまった
    • テストコードも欠いていたが、コミット前にテストするのを忘れていた
    • 対策例: バージョン管理への変更をトリガーにする
      • テストは書いただけでは不十分で、テストを確実に実行する必要がある。バージョン管理への変更をトリガーにタスクを実行する
  • 自動化の裏切り
    • 例: デプロイサービスの設定を「設定ファイル」「UI」の2つで管理しており、設定ファイルのみを修正した結果、サービスが停止した
    • 2つのsource of truthが存在するために起こった問題として、設定ファイルもバージョン管理を唯一のsource of truthとした
      • これを実現するには、CDツールはバージョン管理に設定を保存できるものにする必要がある
  • 原則: ソフトウェアを構成するすべてのプレーンテキストデータをコードのように扱い、バージョン管理に保存する

4章: リントを効果的に使う

  • Pythonゲームのライブラリを提供するゲーム機の開発会社の例
    • ゲームには多くのバグがあり、コンパイルすらできないものもある。そうでないものも未使用の変数を含むなどの問題がある
    • ゲームごとのコードが異なり、一貫性のなさがデバッグを難しくする
    • 対策: リントを導入しこれらのバグに対応する
  • レガシーシステムにlintを導入して時間を有効に使うアプローチ★
    • 既存コードベースにリントを適用すると、膨大な数の問題点を指摘される。「全てを解決する必要はない」「新たな問題の侵入を防ぐだけで状況は改善されている」という2つをポイントに、以下のようなステップでアプローチする
      • Lintの(導入でなく)設定をする。Lintツールの初期設定はプロジェクトにあってないかもしれないので、プロジェクトに適した設定にする (例: コーディング規約)。
      • ベースラインを計測して計測を続ける。全ての問題を解決する必要はなく、時間とともに問題の数が減っていることを確認できれば有意義なことである
        • 報告される問題の数をカウントするスクリプト、カウント結果の可視化ツールなどが必要
      • 計測できるようになれば、新しい変更を加えるたびに計測し、新しい問題を追加する場合はコミットを中止することで、数値の増加を防ぐ
      • Lintの導入と設定・計測を始めれば、これ以上事態は悪化しなくなる。こうなってから、既存の問題に取り組む
  • Lintによる見返りとリスク
見返り リスク
バグを見つけられる 変更による新しいバグの混入の可能性
邪悪なエラーを取り除くのに役立つ Lintの問題の修正は時間がかかる
一貫性のあるコードは保守が容易になる Lintの問題の修正は時間がかかる
  • 隔離:全てを修正すべきではない。
    • 例:
      • 見返り①: 誰もバグを報告しないのであれば投資対効果は小さいかも。
      • 見返り②③: 二度と触れないコードに対し、なぜ時間をかけて新しいバグを引き起こすリスクを冒すのか?修正する対象は、変更が加えられるコードにすべき
    • 具体的な対策例:
      • 隔離: 長期間変更の入っていないコードは frozen というディレクトリに移動し、Lintの対象からも外す
      • 隔離の強制: 隔離したコードに変更が入らないよう、パイプラインなどシステムで強制する
  • どの問題から着手するか?
    • lintの問題の種類: bug / error / style
      • bug: 未初期化の変数や変数の書式の不一致など、望ましくない動作につながるもの
      • error: 未使用の変数など、動作には影響しないがパフォーマンスや保守性に影響するもの
      • style: コードスタイルに一貫性がないこと
    • bug > error > styleの順に優先度が変わるので、bugから着手する
  • linterを開発プロセスに組み込む
    • 開発者が簡単にLinterを使えるようにする
      • Lintの設定ファイルをコードと一緒にコミットし、CDパイプラインと全く同じ設定を使えるようにする
      • 作業中にLinterを実行する。IDEはLinterを統合しているものが多いのでそれを使う。

5章: ノイズの多いテストに対処する

  • アイスクリーム配達会社の例
    • ユーザーが各地域のアイスクリーム業者に注文・配達するため、各アイスクリーム業者の独自のAPIに接続するサービスを提供
    • テストのノイズが多い(頻繁にテストが失敗しエンジニアが失敗を無視する状態)という問題があり、大規模障害を引き起こした
      • ノイズ: 情報を妨げるもの。成功すべきでないが成功したもの、失敗が新しい情報を提供しないものはノイズである
  • どのようにノイズになるか
    • 最初にテストが失敗した時は新しい情報を提供するが、テストの失敗を無視することで(失敗することは既に知っているので)その失敗がノイズに変わる。特に失敗の原因が分からない場合はよく見られる
  • どうやって改善するか: できるだけ早くテストが常に成功する状態を実現し、テストの失敗を新しい情報をもたらすもの(シグナル)とする
    • テストの失敗は全てバグとして扱い、十分に調査する
  • テストが失敗したとき、どうやって修正するか
    • 実際に修正する: テストの失敗する原因を調査し、バグを修正するか、間違ったテストを更新する
    • テストを削除する: 何の価値ももたらさないテストを削除する
    • テストを無効にする: シグナルを隠す一時的な手段、できるだけ早く調査して対応する
    • テストを再試行する: シグナルを隠す一時的な手段、フレーキーテストに対してやりがちな対応。
  • 原則: テストを成功させることがゴールではない
    • コードの実際の動作との不整合を理解し、適切な場所に修正を加えることがゴール

6章: 遅いテストスイートを早くする

  • シンプルなアーキテクチャWebサービスにもかかわらず、新機能を追加するのに数か月かかっている企業の例
    • シンプルなパイプラインの中で、すべてのテストを一度に実行している
    • テストは1日1回、夜間に行われ、問題があるのは翌日にならないとわからない
    • 対策:テストピラミッドに従い、早いテストから先に実行する
      • 一番早いテストを単独で実行できるようにし、そのテストを他のテストよりも先に実行するようにする。例えテストスイート全体がこれまでと同じように遅いとしても、シグナルをある程度早く得られるようになる
  • テストの比率を調整する:単体テストの割合を多く、E2Eテストの割合を少なくするために何ができるか
  • テスト比率を向上する手順
    • 単体テストでカバーされていない行を探す
    • 発見したコードに対し、その行をカバーする単体テストを追加する
    • 遅いテストの中から、追加した単体テストと同じロジックをカバーするテストを見つけ、それらを更新・削除する
  • テストを並列実行することで結合テストの速度を向上する
    • 並列実行できるテストの条件
      • テストはたがいに依存していない
      • テストはどんな順序でも実行可能
      • テストは互いに干渉しない
  • シャーディング:複数のマシンにまたがってテストを並列化し、E2Eテストの速度を向上する

7章: 適切なタイミングで適切なシグナルを送る

  • CIの基礎を全て押さえているにもかかわらずバグや障害に直面している企業
    • PRを作成したタイミングでCIを実行している
  • 変更をプッシュした直後にCIを始める事の欠点
    • 問題がコードベースに追加された後で初めて気づくことになる。なのでコードベースはリリースするのに安全でない状態になる可能性がある
    • CIが壊れたときに変更のプッシュを止めると、全員の作業進捗に影響する
  • マージする前(本章の企業のやり方)にCIを実行する事のメリット
    • 問題が既に追加された後に発見するのでなく、問題がmainブランチに追加されるのを阻止する
    • 悪い変更があったときに全員をブロックすることを避けられる
  • この企業において変更のバグが発生する可能性のある場所
開発作業の時系列 起こりうるバグ
ローカルで変更に取り組み、何度も更新する エラー
フレーキーテスト
mainブランチとの分岐
変更のコミット mainブランチとの分岐
PRの作成 エラー
フレーキーテスト
mainブランチとの分岐
mainブランチにマージ 分岐の統合
本番用のアーティファクトをビルド 依存関係
非決定的なビルド
  • マージ前のCIだけではバグを見逃す
    • mainブランチとの統合: mainブランチへの新しい変更が考慮されない
    • 依存関係の変更: CI実行時と異なるバージョンを使っているかも
    • 非決定性
  • バージョン管理システムによる競合の検出は機能しない場合もある
    • 殆どのバージョン管理システムは、マージするとき、まったく同じ行が変更されていれば間違いに気づくが、それ以上のことはできない
  • PRによるトリガーではまだバグが紛れ込む
    • mainブランチに変更が統合されない時間が延びるほど、競合する変更が混入する
  • mainブランチとの統合に対する解決案: マージ後のCIを導入する
    • マージ後のCIの選択肢
      • mainブランチ上でCIを定期的に実行する: mainブランチに取り込まれて初めてエラーを発見できるので、mainブランチが壊れた状態になる可能性がある
      • mainブランチにマージする前にブランチが最新であることを要求する: mainブランチの更新のたびに他のすべてのPRを更新する必要があり、実際の運用で大きな負担となる場合がある
      • 自動化を使って、マージする前に最新のmainブランチで変更のCIを再実行してからマージする(マージキュー): バージョン管理システムが備えていれば有効だが、自分で実装する場合は複雑になる
  • その他バグの要因①: フレーキーテスト
    • 対策案: 定期的なテストの実行
      • 無関係な作業を妨げることなく、コードやテストにおける非決定的な動作を特定して修正することに役立つ
  • その他バグの要因②: 依存関係の変更
    • 対策案: 全ての環境で同じロジックでビルドとデプロイを行う

3部: デリバリーの簡略化

8章: 簡単なデリバリーはバージョン管理から始まる

  • リリース速度に悩みを抱える企業の例
    • 会社が大きくなるにつれデプロイがリスキーな作業となった
    • 現在は2か月に1度のリリース、リリースの1週間前はコードベースを凍結
  • 解決案①: DORAメトリクスの利用
    • DORAメトリクス: ソフトウェア開発チームのパフォーマンスを評価する4つのキーメトリクスから成り立つ
    • ベロシティに関するメトリクス:
      • デプロイの頻度: 組織が本番環境へのリリースを成功させる頻度
      • 変更のリードタイム: コミットした内容が本番環境へリリースされるまでにかかる時間
    • 安定性に関するメトリクス: サービス復旧時間、変更に伴う障害発生率
  • 解決案②: Trunkベースの開発
    • 変更を早期に取り込み、早期かつ継続的に統合を進められる
    • デプロイメントを改善するには、多くの場合、最初にCIを改善する必要がある
  • より頻繁にコミットするコツ★
    • 作業をどのように分割するとすぐにコミットできるか事前に時間をかけて考え、これをサポートするために必要となる、小規模で自己完結型のPRを作成することに時間をかける
      • 簡単でリスクの低い作業に関するPOCから始める
      • 作業を個別のタスクに分割し、それぞれ数時間~1日以内に完結できるものにする
      • リファクタリングは別のPRで実行しすぐマージする
      • 1つの大きなfeatureブランチでの作業を避けられない場合、コミットバック(ロールバック?)できる部分に注目し、それらの個別のPRを作成・マージする
      • featureフラグ、ビルドフラグの利用

9章: 安全かつ信頼性のあるビルド

  • ビルド担当者が転職してしまった企業の例
    • ビルドプロセスはドキュメントとして定義されている
    • この機会にビルドプロセスを改善したい
  • 安全で信頼性の高いビルドの特徴 (SLSAに基づく)
    • 常にリリース可能: ソースコードは常にリリース可能な状態
    • 自動ビルド: ビルドの実行は自動化されている
    • コードとしてビルドする: ビルド構成をコードのように扱い、バージョン管理システムに保存
    • CDサービスを利用する: 開発者のワークステーションなどだけでなくCDサービスを介して実行される
    • 一時的なビルド環境: ビルドごとに作成・破棄される一時的な環境で実行される
  • 常にリリース可能: CIを駆使してリリース可能な状態に保つ
  • 自動ビルドの2つの要件
  • CDサービスの利用: どんなCDシステムを使うべきか
    • 可能であれば、タスクを分離して実行する手段のデファクトスタンダードになりつつある、コンテナベースの実行をサポートするCDシステムを選択する
  • リリースのバージョン管理を行わないと問題が発生する
    • サービスリリースにおける影響度の違いを区別できない
    • あるチームがどのバージョンのリリースを使うか制御する方法がない
    • リリース間でどんな変更があったか自動的に伝達する方法がない
  • セマンティックバージョニング
    • Major/Minor/Patchを使い分けることで、サービスのリリースにおける影響度の違いを区別できる
    • スクリプトでバージョン情報を使うため、現在のバージョンをリポジトリ内のファイルに保存し、ユーザー向けの変更が発生した場合は、バージョンの値を上げる。更新がないときはパイプラインを失敗させる

10章: 自信を持ってデプロイを行う

  • 定期的な障害に悩まされる企業の例
    • DBとモノリシックサービスというシンプルなアーキテクチャ
    • デプロイ直後に障害が発生する
  • 安定性に関するDORAメトリクス
    • サービス復旧時間: 組織が本番環境で発生した障害から回復するまでにかかる時間
    • 変更に伴う障害発生率: 本番環境で失敗を引き起こすデプロイの割合
  • デプロイ頻度を増やすと各デプロイのリスク量は減少する。各デプロイに含まれる変更量が少なくなるため、本番環境で障害を引き起こす変更がデプロイに含まれる可能性は低くなる
  • デプロイ頻度を上げるためのステップ
    • 変更前:毎週木曜日の午後にデプロイを開始、ローリングアップデート
    • 変更前の課題:
      • 問題を修正するのに数日程度の長い時間が必要である
    • 解決案①: 問題が修正されるまで時間をかけず、問題を軽減する方法を見つける
    • 解決案②: continuous deploymentの採用
      • プロジェクトが満たすべき条件
        • サービスへのリクエストの一定の割合が失敗することを許容する
        • 規制要件を妨げていない
        • リリース前に探索的テストが要求されない
        • リリース前に明示的な承認が要求されない
        • ソフトウェアのリリースに伴ってハードウェアの変更が要求されない

4部: 継続的デリバリーのデザイン

11章: 継続的デリバリーを始める

  • まっさらな状態のプロジェクトに継続的デリバリーを導入するときの順序例
  • レガシープロジェクトの場合
    • 段階的な目標の設定:
      • 何かが壊れたときにそれを検知できるようにする
        • ソースコードがビルドできているかを知るために十分な自動化を追加する
        • 改善したい部分とそうでない部分を分離する
        • テストを追加してカバレッジを計測する
      • より頻繁に、より早くリリースできるようにする
        • 既存プロセスの自動化に集中するか、ゼロから構築するか決める
        • 既存プロセスの場合は1か所ずつ段階的に取り組む
        • ゼロから構築する場合は既存プロセスからの移行の影響を押さえて安全に移行する手法を設計する
    • 課題に焦点を当てることで、取り組むべき順番を整理できる場合がある
    • レガシープロジェクトに継続的デリバリーを導入していくときは、全てを完璧に仕上げようとするのではなく、必要な形を模索して受け入れることが重要

12章: スクリプトはコードでもある

  • CDパイプラインで最近トラブルの発生した企業の例
    • パイプラインが失敗したときに原因を調べるのに時間がかかる
    • スクリプトの中身が理解が難しく、変更を加えるとどうなるかわからない
  • 巨大なスクリプトのリスク:
    • スクリプトが複数のタスクを含む巨大なものだと、「パイプライン全体が失敗したか、成功したか」という1つのシグナルしか得られない。
    • 複数の個別のシグナルが得られる状態にするため、「パイプラインから得たい個々のシグナルごとに、それぞれタスクを切り分けていく」
  • 上手く設計されたタスクの特徴
    • まとまりがある: 1つのことをしっかりこなす
    • 疎結合である: 再利用可能で、他のタスクと組み合わせることができる
    • 意図と定義が明確なインターフェイスを持っている: インプットとアウトプット
    • 必要十分である: 少なすぎず多すぎない
  • 1つのタスクの処理が多すぎるときにあらわれる兆候: これらの処理はパイプラインで処理するほうが良い
    • 他のタスクと重複している部分がある
    • 複数の処理を管理して調整するロジックがタスクに組み込まれている
  • タスクはまとまりがあって疎結合であり、パイプラインはそのロジックを組み合わせて機能させるのがあるべき姿
  • bashが継続的デリバリーに使われるのが最適でない兆候

13章: パイプラインのデザイン

  • パイプラインの実行に時間がかかり、CD自動化への印象が悪くなりつつある企業の例
    • 3つのパイプライン(CI / E2Eテスト / リリース)
    • CIには満足しているが残り2つはそうではない
      • E2E: 夜間に1回実行されるので、結果がすぐにわからない、リリース可能な状態か確信を持てない。完了まで1時間以上かかる。
      • リリース: リリースの準備ができたときだけ実行されるので、何か問題があるとそちらに奔走してリリースが頻繁に中断される
  • CDパイプラインの問題カテゴリ
    • エラー: パイプラインが本来の役割を果たせない
    • スピード: パイプラインが遅いことは、チームが必要な時にパイプラインを実行することを妨げる
    • シグナル: 遅すぎるシグナルなど
  • CDシステムが備えていた方が良い機能
    • タスクとパイプラインのサポート
    • アウトプット: 他のタスクが利用可能な結果を出力する
    • インプット: タスク・パイプラインが入力を利用できる
    • 条件付きの実行
    • Finallyの挙動: 常時実行させるタスク
    • 並列実行
    • マトリックスベースの実行
    • パイプラインから他のパイプラインへの呼び出し: パイプライン自体が再利用可能である

GitLab extendsキーワードでJobを再利用する

今回はGitLabの extends というキーワードを使ってみました。

docs.gitlab.com

背景

GitLab CIはJobを再利用する方法として複数の手法を提供しており、 extends はそのひとつです。

Jobを再利用する方法は、大きく3種類あります (include等も含めるともっとあると思います) が、現在は extends の利用が推奨されているようです。

  • YAML Anchor: YAMLアンカー (&) を使うことで、特定のJobの設定や処理を他のJobに利用できます (ドキュメント) 。
  • !reference: YAMLアンカーと異なり include で呼び出した設定を対象にすることができます (ドキュメント) 。
  • extends: 上述の2つと同様の機能を提供します。

また extends 等と組み合わせるJobは、通常のJobに加えてHidden Jobというものも利用できます。これはJob名の先頭に . をつけることでそのJobを起動しないようにしたものですが、これと extends を組み合わせることでJobの設定の再利用が可能です。

検証

Hidden Jobの動き

まずは extends の前にHidden Jobの動きを見ておきます。以下のような .gitlab-ci.yml を用意してPipelineを起動してみます。

test-job-not-hide:
  script:
    - echo "This job will be executed."

.test-job-hide:
  script:
    - echo "This job is hidden."

Pipelineを起動した結果、 .test-job-hide のほうは実行されないことを確認できます。

extendsによるJobの再利用

次に extends を使ってみます。ここでは以下のように test-job-from-hide-job というJobから .test-job-hide の設定を extends で呼び出します。

test-job-not-hide:
  script:
    - echo "This job will be executed."

.test-job-hide:
  script:
    - echo "This job is hidden."

test-job-from-hide-job:
  extends: .test-job-hide

上記設定でPipelineを実行したところ、 extends で呼び出したJobの内容が実行されるのを確認できます。

また extends の設定は、呼び出したJobの設定で上書きできます。今度は以下のように extends で呼び出すJobのscriptを上書きした場合を見ます。

test-job-not-hide:
  script:
    - echo "This job will be executed."

.test-job-hide:
  script:
    - echo "This job is hidden."

test-job-from-hide-job:
  extends: .test-job-hide

test-job-overwrite:
  extends: .test-job-hide
  script: # Hide Jobの内容を上書きする
    - echo "This job will overwrite hide job setting."

上記設定でPipelineを実行した結果、上書きしたscriptの内容が実行されることを確認できます。

なお、 extends はhideでない通常のJobも指定できます。その場合は呼び出し先のJobも実行されます。

includeとextendsの組み合わせ

最後に include キーワードと組み合わせた場合を見てみます。

※参考:

techstep.hatenablog.com

ここでは以下のような test.yml を用意し、 .gitlab-ci.yml からこれを呼び出します。

test.yml

.test:
  script: 
    - echo "This is from hidden job."

.gitlab-ci.yml

test-job-not-hide:
  script:
    - echo "This job will be executed."

.test-job-hide:
  script:
    - echo "This job is hidden."

test-job-from-hide-job:
  extends: .test-job-hide

include:
  - local: test.yml

test-job-include-hidden-job:
  extends: .test

上記設定でPipelineを実行した結果、 test.yml の内容が実行されるのを確認できます。

その他

extends は多重に呼び出すことも可能です。例えばHidden Jobの中でも extends キーワードを使って別のJobの設定を呼び出したり、呼び出し先のJobでさらに extends を使うこともできます。 extends は最大11重まで使えるようですが、ドキュメントでは多くても3重までに抑えるよう記載しています。

docs.gitlab.com

GitLab Comment templateを使ってみる

今回はGitLabのComment templateを触ってみました。

docs.gitlab.com

背景

GitLab Comment templateはIssue / Merge request / Epic等のコメント部分のテンプレートを提供する機能で、個人またはProject/Group単位でよく使うコメントなどを定義し、コメントを記載する手間を省くことができます。

なおGitLab Freeプランでは個人レベルでテンプレートを用意できますが、Premium / UltimateプランではProject / Groupレベルで共有できるテンプレートを利用できます。

検証

ここではFreeプランでComment templateを簡単に動かしてみます。

Comment templateは ユーザー設定 の左メニューにある コメントテンプレート から設定します。テンプレートを追加するため 新規を追加 を選択します。

テンプレートの作成画面は以下の通りです。ここではテンプレート名、テンプレートの内容を設定できます。

テスト用のテンプレートを設定後、例えばIssueの画面に移動します。このうち赤枠で囲った部分を押すと、設定したComment templateが表示されます。

設定したテンプレートの一覧が表示されるので、使いたいテンプレートを選択します。

テンプレートに設定した内容が表示されるので、このままコメントするか、更にコメントを追加することができます。

また以下のようにマークダウン形式でコメントしておくことで、EpicへのURLを追加するだけでコメントを完了させることもできそうです。

GitLab Protected branchを試す

今回はGitLabのProtected branchという機能を試しました。

docs.gitlab.com

背景

GitLabに限らず、ブランチに対する操作権限をコントロールすることは、リポジトリを安全に運用することにつながります。GitLabでは Protected branch という機能でブランチの保護を実現しており、この機能を使うと、例えばあるリポジトリへの変更はすべてMerge requestを経由しないとできない、といったコントロールが可能になります。どういったコントロールが可能かというと、大まかにいえば 誰が どのブランチに対して どんな操作を 許可するかを設定します。

検証

今回はGitLab Self-managed版 (Freeプラン) で protected-branch-test というProjectを作成し検証しています。また検証のため test というユーザーに Developer Roleを付与、 feature というブランチを事前に作成します。

Protected branchを設定するにはProjectの settings から Repository を選択し、Protected branches という項目があるので選択します。

Add protected branch を選択します。

遷移先の画面でProtected branchの設定を行います。設定項目は以下の通りです。設定後は Protect を選択します。

  • Branch : 対象のブランチを指定します。ワイルドカードを使って複数ブランチの指定も可能です。
  • Allowed to merge : mergeを許可するRoleを指定します。
  • Allowed to push and merge : push/mergeを許可するRoleを指定します。
  • Allowed to force push : 全てのユーザーにForce pushを許可するか選択します。

GitLabのドキュメントでは、以下の3つのパターンについて紹介しています。

  • 全員にMerge requestを要求する : Allowed to mergeDevelopers + Maintainers に、 Allowed to push and mergeNo one に設定します。
  • 全員に直接Pushを許可する : Allowed to push and mergeDevelopers + Maintainers に設定します。
  • 全員にForce pushを許可する : Allowed to force push を有効にします。

ここでは以下のようなProtected branchを設定します。

設定後は以下のように表示されます。なお設定を削除するには該当のブランチの Unprotect を選択します。

ここでは試しに test ユーザーで feature ブランチから main ブランチにMerge requestを作成します。この時は main ブランチに対するマージ権限は Maintainers にしか付与されていないので、 Developer Roleである test ユーザーにはマージはできないはずです。

Merge request作成後の画面を見ると Merge ボタンが見当たらず、 test ユーザーに割り当てられた Developer Roleではマージが許可されていないことを確認できます。

なおAdministratorユーザーに切り替えてMerge requestを見ると Merge ボタンを確認できます。

また、 test ユーザーで main ブランチ宛に新規ファイルを作成しようとしても、以下のようにエラーが表示されます。これは main ブランチに対する Allowed to push and merge の権限が Maintainer にしか付与されていないからです。

次にブランチ指定時にワイルドカードを使った場合を試します。事前に以下のように feature で始まるブランチを複数用意します。

Protected branch設定時は以下のようにワイルドカードを使用します。

すると以下のように 3 matching branches という記載が見えます。

これを選択すると以下の画面に遷移し、3つの feature* ブランチに適用されているのを確認できます。

さて、Protected branchを適用されたブランチに対し、以下のように削除をしようとします。

すると以下のように警告画面が表示され、削除するにはブランチ名を入力する必要があります。このようにProtected branchを有効にしたブランチは、誤って削除されることのないよう、削除時にチェック項目が追加されます。

なおprotectされていない場合は以下のような画面です

その他

Premium/Ultimateプランでは以下の機能も利用できます。

  • Protected branchに対し、最低1件のCode Ownerからの承認を要求できます。
  • Group/Userを Allowed to merge / Allowed to push and merge に追加できます。
  • Group内の全てのProjectに共通のProtected branchを追加できます。

Amazon CodeWhispererでCloudFormationコードを生成してみる

今回はAWS製のコード生成ツールであるAmazon CodeWhispererを使ってみました。

docs.aws.amazon.com

背景

Amazon CodeWhispererは機械学習を利用したコード生成を実行するサービスであり、コードを開発しながらリアルタイムにコードをサジェストしてくれます。コード生成サービスは複数ありますが、AWS公式ドキュメントに従えば、CodeWhispererは以下のような特徴を有しています。

  • AWSサービスで使用するために最適化されている: AWS API向けに最適化されたコードを提案し、AWSの各種サービスの効率的な使用を支援します。Amazonのコードをトレーニングに使っていることもあり、関連するクラウドサービスやライブラリを用いた提案、AWSのベストプラクティスを満たすコードの提案などを行います。
  • セキュリティスキャンとコード修復: CodeWhispererにはセキュリティスキャン機能を備えており、脆弱性の発見とそれを修正するコードを提案します。
  • エンタープライズ管理: CodeWhispererは、組織で利用するProfessional版を提供しており、AWS IAM Identity Centerを利用したSSOの提供、リファレンス付きコードの提案などを利用できます。

本記事投稿時点のCodeWhispererの対応する言語・IDEは以下の通りです。なお、言語によって訓練データが異なるため、(生成するコードの精度など?の) サポートの度合いは異なるようです。

検証

今回はVisual Studio CodeにCodeWhispererをインストールし、AWS CloudFormationを例に少しいじってみます。

Visual Studio CodeでCodeWhispererを利用するには、 AWS Toolkit for VSCodeをインストールします。

インストール後はサインインのオプションを選択する画面が表示されます。ここではCodeWhispererを利用するため Amazon Q + CodeWhispererUse for free, no AWS Account required を選択します。

CodeWhispererを無料で使うにはAWS Builder IDを使用します。事前に用意している場合はそれを利用しますが、まだの場合は作成から行います。

ログインしてVSCode画面に戻ると、以下のようにAmazon Qの画面が表示されます。これでCodeWhispererを利用する準備は整いました。

インストール後は以下のように操作します。

  • 手動でCodeWhispererを実行: Alt + c ボタン (Windows) / Option + c ボタン (Mac)
  • 提案をすべて了承する: Tab ボタン
  • 提案を1文字ずつ了承する: Ctrl + ボタン
  • 次の提案を表示する: ボタン
  • 前の提案を表示する: ボタン
  • 提案を否定する: ESC / back space ボタン

例えばUserdataを使用するEC2インスタンスの定義ファイルを生成するようコメントします。ここで改行 (または Alt+C ボタンを押下) すると CodeWhisperer is generating… と表示され、しばらくするとコードの候補が生成されます。コードに問題がなければ Tab または Ctrl + ボタンを押せばコードが書けます。

また、AWS Toolkit for VSCodeをインストールするとCodeWhispererと一緒にAmazon Qも利用できます。例えば表示しているコードの内容を説明するようチャット欄に記入すると、以下のように説明してくれます。

ただし、新しいリソースには対応していない場合もあります。例えば昨年登場したEKS Pod Identityに関するリソースを生成するよう指定しても、想定とは異なるコード (ここで生成されたリソースは実際には存在しません) が生成されました。

なお、CodeWhispererを使ったコードの開発は、AWSブログなどでも紹介されています。

さいごに

今回少しだけ触ってみましたが、コメントの内容に沿ってコードを生成するといっても、そもそも「あるリソースを」「どのような設定で」作成したいかが明確でないと、思ったようなコードを生成するのが難しいと感じました。なので、ある程度AWS CloudFormationを使い慣れており、用意したいリソースや設定も把握している人にとっては、作成するリソースの「大まかな雛形」を作成するには役立つのでは、と思います。ただし、あくまでCloudFormationを作成しただけの感想なので、もしかしたら汎用的なプログラミング言語はもっと違った結果なのかもしれません。例えばTypeScriptでAWS CDKを使ってリソースを定義するのはもっと楽になったりするのかもしれません。

一方で、ある程度書き進めていくと生成されるコードの精度が向上している感覚も得られました。分かりやすい例でいうと、 Parameters で複数の変数を設定後、それを !Ref で参照しようとすると、書きたいものが入力候補として最初に表示される、という体験は何度もありました。また、Resources.XXX.Type まで打った後に必要な Properties を設定するときは、設定しようと思ったパラメータ名が候補として登場する機会も多かったです。こういった体験もあり、「既にあるコードに手を加える」、あるいは「ある一定の量のコードを書き進めた後」の場合、CodeWhispererのいい部分が見えてきたと感じました。

なお、CodeWhispererに限らず生成AIサービスの精度は、時間が経つにつれてさらに改善されるものだと思うので、しばらくは使い続けたいと思います。

GitLabパイプラインエディタを紹介する

今回はGitLabのパイプラインエディタについて紹介します。

docs.gitlab.com

背景

パイプラインエディタはGitLabのUIから .gitlab-ci.yml を編集・テストできる機能です。具体的には以下のような機能を提供しています。

  • Edit : パイプラインエディタ画面では .gitlab-ci.yml を直接修正し、コミットすることが可能です。
  • Validate : .gitlab-ci.yml の編集中は、CI/CD パイプラインのベーシックな構文チェックが実行され、誤った箇所を指摘します。また Validate タブで検証を実施すると、 rules needs などのロジックに不適切な部分がないか、より詳細にチェックできます。
  • Visualize : Visualize タブでは、.gitlab-ci.ymlで定義したパイプラインが可視化されます。また include キーワードを使っている場合は、参照先のファイルも確認できます。

パイプラインエディタは gitlab-ci.yml に対する構文チェックをリアルタイムで行いつつ、パイプラインの修正を可能にします。GitLabに限らずCI/CDフローの定義ファイルを検証するには、修正後にコミットして実際に動かすことしかチェックできないこともあります。この機能は、実際にテストする前に構文チェックを行い、タイポなど単純なミスに素早く気付くことができます。

検証

今回は pipeline-editor-test というProjectで検証をしました。

ここでは以下のような .gitlab-ci.yml を使用します。

stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - echo "This is build stage."

test:
  stage: test
  script: 
    - echo "This is test stage."
  
deploy:
  stage: deploy
  script: 
    - echo "This is deploy stage."

パイプラインエディタの表示

パイプラインエディタは ビルドパイプラインエディタ から表示します。

パイプラインエディタは以下の4つのタブを利用できます。

  • 編集 : ここでは画面上で .gitlab-ci.yml を編集できます。また編集中に構文的に問題があると、その旨を画面上に表示します。また変更内容をコミットしてパイプラインを起動したり、パイプライン画面にも移動できます。
  • 視覚化 : ここでは .gitlab-ci.yml の内容を可視化し、視覚的に理解できるようサポートします。例えば .gitlab-ci.ymlneeds が設定されていると、Job間の依存関係が線で表現されます。また include を含む場合、対象のファイルに移動もできます。
  • 検証 : ここでは .gitlab-ci.yml の挙動をシミュレーションし、想定通り動作するか確認できます。
  • 完全な設定 : ここでは 編集 での自動チェックよりもさらに詳細なチェック (ロジックを含む) を行うことができます。

まず 編集 では自動的に .gitlab-ci.yml の構文チェックを行い、問題があればUIに表示します。

視覚化 では、以下のように各Jobがどのstageで実行されるか表示されます。

検証 では以下のような画面が表示されます。検証時点では デフォルトブランチへのGitプッシュイベント しか選択できませんが、 パイプラインの検証 を選択します。

すると .gitlab-ci.yml に対してシミュレートを行い、問題なければ以下のように表示されます。

最後に 完全な設定 では、ロジックを含んだより詳細なチェックを行います。完全な設定では編集はできず閲覧のみ可能です。

新しいJobを追加する

まずは冒頭紹介した .gitlab-ci.yml をパイプラインエディタ上で修正する例です。ここでは test stageに別のJobを追加します。修正後は以下のように、 test Jobを削除して test01 test02 という2つのJobを追加します。

stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - echo "This is build stage."

# 追加したJob
test01:
  stage: test
  script: 
    - echo "This is test01 job."
  
# 追加したJob
test02:
  stage: test
  script: 
    - echo "This is test02 job."

deploy:
  stage: deploy
  script: 
    - echo "This is deploy stage."

ここで修正中の画面の例を出すと、例えば以下の画像ではJobを定義するときに script: または trigger: というキーワードが欠けており、構文エラーであることを表示しています。

これを修正するとエラーは表示されず、代わりにCIの設定が有効であることを表示します。

修正が完了したので、これをリポジトリに反映します。ここでは main ブランチに向けてコミットします。

コミットするとパイプラインが実行され、パイプラインへのリンクも表示されます。

リンクを押してパイプライン画面に移動し、Jobの実行結果などを確認できます。

Job間に依存関係を与える

続いて needs: キーワードを使ってJob間の依存関係を設定する場合を見てみます。なお修正前の .gitlab-ci.yml を可視化すると以下のように表示されます。

ここで以下のように .gitlab-ci.yml を修正してみます。

stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - echo "This is build stage."

test01:
  stage: test
  needs: ["build"] # needsを追加
  script: 
    - echo "This is test01 job."
  
test02:
  stage: test
  script: 
    - echo "This is test02 job."

deploy:
  stage: deploy
  needs: ["test02"] # needsを追加
  script: 
    - echo "This is deploy stage."

needs キーワードを含むと、視覚化 では以下のように、依存関係にあるJob間に線が引かれます。

なお、パイプライン画面でも ジョブの依存関係 を選択することで依存関係は表示されます。

include でテンプレートを呼び出す

最後に include キーワードを含む場合を見てみます。includeについては以前紹介しました。

techstep.hatenablog.com

パイプラインエディタは includeにも対応しており、includeで利用するファイルの表示、 includeで呼び出したJobを含む.gitlab-ci.ymlの表示などができます。

以下の画像では include-local-test というProjectをパイプラインエディタで見た場合を表示しています。このProjectでは2つのテンプレートを include:local で呼び出していますが、 include を含む場合はテンプレートファイルへの名前が表示され、これを選択すると対象のテンプレートの場所まで画面が遷移します。

また 完全な設定 タブに移動すると、include:local で指定したテンプレートの内容も含めた .gitlab-ci.yml の内容が表示されます。ここでは前の画面で表示されていた include: 以降の内容は消え、テンプレートで定義していた test-from-include deploy という2つのJobが追加されています。

GitLab Commit message templateを使ってみる

今回はGitLabのCommit message templateを紹介します。

docs.gitlab.com

背景

Commit message templateは、名称の通りコミット時に利用するテンプレートです。GitLabではMerge commit message / Squash commit messageの2種類のテンプレートが用意されています。

Commit message templateはいくつかの変数が提供されています。

  • %{source_branch}: コミット元の、マージされるブランチ名
  • %{target_branch}: コミット先のブランチ名
  • %{title}: Merge requestのタイトル
  • %{issues}: MRで言及、クローズしたIssue
  • %{reference}: Merge requestへのリファレンス

docs.gitlab.com

それぞれのデフォルトのテンプレートは以下の通りです。

Merge commit message template

Merge branch '%{source_branch}' into '%{target_branch}'

%{title}

%{issues}

See merge request %{reference}

Squash commit message template

%{title}

デフォルトのテンプレートの状態でMerge commitを作成すると、以下のように表示されます。

試しにMerge commit message templateを修正してみます。修正は 設定マージリクエスト 画面から可能です。

修正後にMerge commitを作成すると、以下のようにメッセージが変更されるのを確認できます。