Mozilla プロジェクトが開始されたとき、私は最初に継続的インテグレーション (CI) の概念に出くわしました。プロセスの一部として初歩的なビルド サーバーが含まれており、これは当時としては革新的でした。ビルドとリンクに 2 時間かかった C++ プロジェクトを維持していました。
悪いコードがプロジェクトにコミットされたため、複雑な問題を引き起こすクリーン ビルドを行うことはめったにありませんでした。
その昔から多くのことが変わりました。 CI 製品はいたるところにあり、Java 開発者として、かつてないほど豊富な機能を享受しています。しかし、私は先を行っています... 基本から始めましょう。
継続的インテグレーションは、コードの変更が頻繁かつ一貫した方法で自動的にビルドおよびテストされるソフトウェア開発プラクティスです。
CI の目標は、統合の問題をできるだけ早く見つけて解決し、バグやその他の問題が本番環境に移行するリスクを軽減することです。
CI は、多くの場合、コードの統合から運用環境への展開まで、ソフトウェア配信プロセス全体を自動化することを目的とした継続的配信 (CD) と密接に関連しています。
CD の目標は、新しいリリースとホットフィックスの展開に必要な時間と労力を削減し、チームがより迅速かつ頻繁に顧客に価値を提供できるようにすることです。
CD を使用すると、CI テストに合格したすべてのコード変更は展開の準備ができていると見なされるため、チームはいつでも自信を持って新しいリリースを展開できます。この投稿では継続的デリバリーについては説明しませんが、議論すべきことがたくさんあるので、継続的デリバリーについて説明します。
私はこのコンセプトの大ファンですが、監視する必要があることがいくつかあります。
多くの強力な継続的インテグレーション ツールがあります。一般的に使用されるツールを次に示します。
Jenkins : Jenkins は最も人気のある CI ツールの 1 つで、さまざまなプログラミング言語とビルド ツールをサポートする幅広いプラグインと統合を提供します。これはオープンソースであり、ビルド パイプラインを設定および管理するための使いやすいインターフェイスを提供します。
これは Java で書かれており、しばしば私の「頼りになるツール」でした。ただし、管理とセットアップは面倒です。多少不足しているユーザーエクスペリエンスをクリーンアップする「サービスとしてのジェンキンス」ソリューションもいくつかあります。
すぐに説明する GitHub Actions について言及していないことに注意してください。 CI ツールを比較する際に考慮すべき要素がいくつかあります。
一般に、Jenkins はその汎用性と豊富なプラグイン ライブラリで知られているため、複雑なビルド パイプラインを持つチームに人気があります。 Travis CI と CircleCI は、使いやすさと一般的な SCM ツールとの統合で知られており、小規模から中規模のチームに適しています。
GitLab CI/CD は、統合された CI/CD 機能を提供するため、ソース コード管理に GitLab を使用するチームに人気があります。 Bitbucket Pipelines は、プラットフォームとシームレスに統合されるため、ソース コード管理に Bitbucket を使用するチームに適しています。
エージェントのホスティングは、CI ソリューションを選択する際に考慮すべき重要な要素です。エージェント ホスティングには、クラウドベースとオンプレミスの 2 つの主なオプションがあります。
CI ソリューションを選択するときは、チーム固有のニーズと要件を考慮することが重要です。
たとえば、大規模で複雑なビルド パイプラインがある場合は、Jenkins などのオンプレミス ソリューションが適している場合があります。これにより、基盤となるインフラストラクチャをより細かく制御できるからです。
一方、ニーズが単純な小規模なチームの場合は、セットアップと管理が簡単な Travis CI などのクラウドベースのソリューションが適している可能性があります。
ステートフルネスは、エージェントがビルド間でデータと構成を保持するかどうかを決定します。
最良のアプローチに関して、CI 支持者の間で活発な議論が行われています。ステートレス エージェントは、クリーンで再現しやすい環境を提供します。私はほとんどの場合にそれらを選択し、より良いアプローチだと思います。
ステートレス エージェントはセットアップに時間がかかるため、コストが高くなる可能性もあります。クラウド リソースに対して料金が発生するため、そのコストが加算される可能性があります。しかし、一部の開発者がステートフル エージェントを好む主な理由は、調査機能です。
ステートレス エージェントでは、CI プロセスが失敗すると、通常、ログ以外に調査する手段がなくなります。
ステートフル エージェントを使用すると、マシンにログインして、指定されたマシンでプロセスを手動で実行することができます。失敗した問題を再現し、そのおかげで洞察を得ることができます。
私が一緒に働いていた会社は、Azure がステートフル エージェントを許可していたため、GitHub Actions ではなく Azure を選択しました。これは、失敗した CI プロセスをデバッグするときに重要でした。
賛否両論ありますが、個人的な意見です。バグの調査よりも、不適切なエージェントのクリーンアップのトラブルシューティングに多くの時間を費やしたと感じています。しかし、それは個人的な経験であり、私の知的な友人の中には反対する人もいます.
再現可能なビルドとは、ビルドが実行される環境や時間に関係なく、ビルドが実行されるたびにまったく同じソフトウェア成果物を生成する機能を指します。
DevOps の観点からは、反復可能なビルドを持つことは、ソフトウェアの展開の一貫性と信頼性を確保するために不可欠です。
断続的な障害はどこでも DevOps の悩みの種であり、追跡するのは困難です。
残念ながら、簡単な修正はありません。私たちが望む限り、適度に複雑なプロジェクトには不安定さが見られます。これを最小限に抑えるのが私たちの仕事です。反復可能なビルドには 2 つのブロッカーがあります。
依存関係を定義するときは、特定のバージョンに注目する必要があります。多くのバージョニング スキームがありますが、過去 10 年間で、標準的な 3 つの数字のセマンティック バージョニングが業界を席巻しました。
このスキームは、その使用がビルドの再現性に大きな影響を与える可能性があるため、CI にとって非常に重要です。
<dependency> <groupId>group</groupId> <artifactId>artifact</artifactId> <version>2.3.1</version> </dependency>
これは非常に具体的で、再現性に優れています。ただし、これはすぐに時代遅れになる可能性があります。バージョン番号をLATEST
またはRELEASE
に置き換えると、現在のバージョンが自動的に取得されます。ビルドが繰り返されなくなるため、これは悪いことです。
ただし、ハードコーディングされた 3 つの数値のアプローチにも問題があります。パッチ バージョンがバグのセキュリティ修正を表す場合がよくあります。その場合、最新のマイナー アップデートにアップデートする必要がありますが、新しいバージョンにはアップデートしません。
たとえば、前のケースでは、バージョン2.3.2
を暗黙的に使用し、 2.4.1
使用したくありません。これは、マイナーなセキュリティ アップデートやバグの再現性と引き換えに行われます。
しかし、より良い方法は、Maven Versions Pluginを使用し、定期的にmvn versions:use-latest-releases
コマンドを呼び出すことです。これにより、バージョンが最新に更新され、プロジェクトが最新の状態に保たれます。
これは、繰り返し可能なビルドの簡単な部分です。難しさは不安定なテストにあります。これは非常に一般的な問題であり、一部のプロジェクトでは失敗したテストの「妥当な量」を定義し、一部のプロジェクトでは失敗を認める前にビルドを複数回再実行しています。
テストの不安定さの主な原因は、状態の漏れです。以前のテストからのわずかな副作用が残っているため、テストが失敗する可能性があります。理想的には、各テストが分離して実行されるように、テストはそれ自体の後にクリーンアップする必要があります。
完全に分離された新しい環境ですべてのテストを実行するのが理想的ですが、これは現実的ではありません。これは、テストの実行に時間がかかりすぎることを意味し、CI プロセスを長時間待つ必要があります。
さまざまな分離レベルでテストを作成できます。場合によっては、完全な分離が必要であり、テストのためにコンテナーをスピンアップする必要がある場合があります。しかし、ほとんどの場合、そうではなく、速度の違いは重要です。
テスト後のクリーンアップは非常に困難です。場合によっては、データベースなどの外部ツールからの状態リークにより、不安定なテストの失敗が発生することがあります。失敗の再現性を確保するために、テスト ケースを一貫して並べ替えるのが一般的な方法です。これにより、今後のビルドの実行が同じ順序で実行されることが保証されます。
これは熱く議論されているトピックです。一部のエンジニアは、これがバグのあるテストを助長し、テストのランダムな順序でしか発見できない問題を隠していると考えています。私の経験から、これは確かにテストでバグを見つけましたが、コードでは見つけませんでした。
私の目標は完璧なテストを作成することではないので、アルファベット順などの一貫した順序でテストを実行することを好みます。
テストの失敗の統計を保持することが重要であり、単純に再試行を押してはいけません。問題のあるテストと失敗の実行順序を追跡することで、多くの場合、問題の原因を見つけることができます。
ほとんどの場合、失敗の根本原因は前のテストでのクリーンアップの失敗が原因で発生します。そのため、順序が重要であり、その一貫性も重要です。
私たちは、CI ツールではなく、ソフトウェア製品を開発するためにここにいます。 CI ツールは、プロセスを改善するためにここにあります。残念ながら、多くの場合、CI ツールでの経験は非常に苛立たしいものであり、実際にコードを記述するよりもロジスティクスに多くの時間を費やすことになります。
多くの場合、変更をマージできるように CI チェックに合格するために何日も費やしました。私が近づくたびに、別の開発者が最初に変更をマージし、私のビルドを壊してしまいました。
これは、特にチームが規模を拡大し、変更をマージするよりも CI キューで多くの時間を費やす場合に、開発者のエクスペリエンスが優れていないことにつながります。これらの問題を軽減するためにできることはたくさんあります。
最終的に、これは開発者の生産性に直結します。しかし、この種の最適化のためのプロファイラーはありません。毎回測定する必要があります。これは骨の折れる作業です。
GitHub Actions は、GitHub に組み込まれた継続的インテグレーション/継続的デリバリー (CI/CD) プラットフォームです。エージェントのセルフホスティングをある程度許可しますが、ステートレスです。オープンソース プロジェクトでは無料で、クローズド ソース プロジェクトではかなりの無料割り当てがあるため、私はそれに注目しています。
この製品は、この分野では比較的新しい候補であり、前述の他のほとんどの CI ツールほど柔軟ではありません。ただし、GitHub およびステートレス エージェントとの緊密な統合のおかげで、開発者にとっては非常に便利です。
GitHub アクションをテストするには、新しいプロジェクトが必要です。この場合、 JHipsterを使用して、次の構成で生成しました。
ここで、GitHub Actions の使用を示す別のプロジェクトを作成しました。どのプロジェクトでもこれに従うことができることに注意してください。この場合、Maven の手順を含めますが、概念は非常に単純です。
プロジェクトが作成されたら、GitHub でプロジェクト ページを開き、[アクション] タブに移動できます。
次のようなものが表示されます。
右下隅に、Java with Maven プロジェクト タイプが表示されます。このタイプを選択したら、次のようにmaven.yml
ファイルの作成に進みます。
残念ながら、GitHub が提案するデフォルトの maven.yml には問題があります。これは、この画像に表示されるコードです。
name: Java CI with Maven on: push: branches: [ "master" ] pull_request: branches: [ "master" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 11 uses: actions/setup-java@v3 with: java-version: '11' distribution: 'temurin' cache: maven - name: Build with Maven run: mvn -B package --file pom.xml # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - name: Update dependency graph uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
最後の 3 行は、依存関係グラフを更新します。しかし、この機能は失敗するか、少なくとも私にとっては失敗しました。それらを削除すると問題が解決しました。コードの残りの部分は、標準の YAML 構成です。
コードの上部近くにあるpull_request
およびpush
行は、ビルドがプル リクエストとマスターへのプッシュの両方で実行されることを宣言します。これは、コミットする前にプル リクエストでテストを実行できることを意味します。テストが失敗した場合、コミットしません。
プロジェクト設定で、失敗したテストでのコミットを禁止できます。 YAML ファイルをコミットしたら、プル リクエストを作成できます。システムがビルド プロセスを実行します。これには、maven の「package」ターゲットがデフォルトでテストを実行するため、テストの実行が含まれます。
テストを呼び出すコードは、末尾近くの「run」で始まる行にあります。これは事実上、標準の Unix コマンド ラインです。場合によっては、シェル スクリプトを作成し、CI プロセスから実行するだけでよい場合があります。
すべての YAML ファイルとさまざまな CI スタックの構成設定を処理するよりも、優れたシェル スクリプトを作成する方が簡単な場合があります。
また、将来 CI ツールを切り替えることを選択した場合の移植性も高くなります。ただし、現在のニーズには maven で十分なので、ここでは必要ありません。
ここで成功したプル リクエストを確認できます。
これをテストするために、 “/api”
エンドポイントを“/myapi”
に変更して、コードにバグを追加できます。これにより、以下に示す障害が発生します。また、コミットの作成者に送信されるエラー メールもトリガーされます。
このような障害が発生した場合は、右側の「詳細」リンクをクリックできます。これにより、次のエラー メッセージが直接表示されます。
残念ながら、これは通常、問題の解決に役立つ役に立たないメッセージです。ただし、上にスクロールすると、実際の失敗が表示されます。これは通常、次のように強調表示されているので便利です。
多くの場合、複数の障害が発生するため、さらに上にスクロールすることをお勧めします。このエラーでは、ここで確認できる AccountResourceIT の394
行目のアサーションで失敗したことがわかります。行番号が一致していないことに注意してください。この場合、 394
行目はメソッドの最後の行です。
@Test @Transactional void testActivateAccount() throws Exception { final String activationKey = "some activation key"; User user = new User(); user.setLogin("activate-account"); user.setEmail("[email protected]"); user.setPassword(RandomStringUtils.randomAlphanumeric(60)); user.setActivated(false); user.setActivationKey(activationKey); userRepository.saveAndFlush(user); restAccountMockMvc.perform(get("/api/activate?key={activationKey}", activationKey)).andExpect(status().isOk()); user = userRepository.findOneByLogin(user.getLogin()).orElse(null); assertThat(user.isActivated()).isTrue(); }
これは、アサート呼び出しが失敗したことを意味します。 isActivated()
はfalse
を返し、テストに失敗しました。これは、開発者が問題を絞り込み、根本原因を理解するのに役立ちます。
前述したように、CI は開発者の生産性に関するものです。単にコンパイルしてテストするだけではありません。コーディング標準の適用、コードのリント、セキュリティの脆弱性の検出などを行うことができます。
この例では、強力なコード分析ツール (linter) である Sonar Cloud を統合してみましょう。プロジェクトの潜在的なバグを見つけて、コードの品質を向上させるのに役立ちます。
SonarCloud は、開発者がコードの品質、セキュリティ、および保守性に関連する問題を見つけて修正するために、コードを継続的に検査および分析できる SonarQube のクラウドベース バージョンです。 Java、C#、JavaScript、Python など、さまざまなプログラミング言語をサポートしています。
SonarCloud は、GitHub、GitLab、Bitbucket、Azure DevOps などの一般的な開発ツールと統合されています。開発者は SonarCloud を使用して、コードの品質に関するリアルタイムのフィードバックを取得し、全体的なコードの品質を向上させることができます。
一方、SonarQube は、ソフトウェア開発者向けの静的コード分析ツールを提供するオープンソース プラットフォームです。コード品質の概要を示すダッシュボードを提供し、開発者がコード品質、セキュリティ、および保守性に関連する問題を特定して修正するのに役立ちます。
SonarCloud と SonarQube はどちらも同様の機能を提供しますが、SonarCloud はクラウドベースのサービスでサブスクリプションが必要ですが、SonarQube はオンプレミスまたはクラウド サーバーにインストールできるオープンソース プラットフォームです。
簡単にするために、SonarCloud を使用しますが、SonarQube は問題なく動作するはずです。開始するには、 sonarcloud.ioにアクセスしてサインアップします。理想的には、GitHub アカウントを使用します。次に示すように、Sonar Cloud による監視用のリポジトリを追加するオプションが表示されます。
Analyze new page オプションを選択するときは、GitHub リポジトリへのアクセスを承認する必要があります。次のステップは、ここに示すように、Sonar Cloud に追加するプロジェクトを選択することです。
選択してセットアップ プロセスに進むと、分析方法を選択する必要があります。 GitHub Actions を使用するため、次のステージでそのオプションを選択する必要があります。
これが設定されると、次の図に示すように、Sonar Cloud ウィザード内で最終段階に入ります。コピー可能なトークンを受け取り (画像ではエントリ 2 がぼやけています)、すぐにそれを使用します。
「Maven」というラベルの付いたボタンをクリックすると表示される、maven で使用するデフォルトの手順もあることに注意してください。
GitHub のプロジェクトに戻ると、プロジェクト設定タブに移動できます (トップ メニューのアカウント設定と混同しないでください)。ここでは、次に示すように「シークレットと変数」を選択します。
このセクションでは、新しいリポジトリ シークレット、具体的には、ここで確認できるように、SonarCloud からコピーした SONAR_TOKEN キーと値を追加できます。
GitHub リポジトリ シークレットは、GitHub リポジトリに関連付けられた機密情報 (API キー、トークン、パスワードなど) を開発者が安全に保存できるようにする機能です。これらの情報は、リポジトリで使用されるさまざまなサードパーティ サービスまたはプラットフォームへのアクセスを認証および承認するために必要です。 .
GitHub リポジトリ シークレットの背後にある概念は、コードや構成ファイルで情報を公開することなく、機密情報を管理および共有するための安全で便利な方法を提供することです。
シークレットを使用することで、開発者は機密情報をコードベースから分離して保持し、セキュリティ違反や不正アクセスが発生した場合に機密情報が公開されたり侵害されたりするのを防ぐことができます。
GitHub リポジトリ シークレットは安全に保管され、リポジトリへのアクセスを許可された承認済みのユーザーのみがアクセスできます。シークレットは、リポジトリに関連付けられたワークフロー、アクション、およびその他のスクリプトで使用できます。
それらは環境変数としてコードに渡されるため、安全で信頼できる方法でシークレットにアクセスして使用できます。
全体として、GitHub リポジトリ シークレットは、開発者がリポジトリに関連付けられた機密情報を管理および保護するためのシンプルで効果的な方法を提供し、プロジェクトとそれが処理するデータのセキュリティと整合性を確保するのに役立ちます。
これをプロジェクトに統合する必要があります。まず、これらの 2 行を pom.xml ファイルに追加する必要があります。組織名を自分の名前に合わせて更新する必要があることに注意してください。これらは、XML のセクションに入る必要があります。
<sonar.organization>shai-almog</sonar.organization> <sonar.host.url>https://sonarcloud.io</sonar.host.url>
作成した JHipster プロジェクトには、このコードが機能する前にpom ファイルから削除する必要があるSonarQube サポートが既に含まれていることに注意してください。
この後、 maven.yml
ファイルの「Build with Maven」部分を次のバージョンに置き換えることができます。
- name: Build with Maven env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=shai-almog_HelloJHipster package
これを行うと、SonarCloud は、次に示すように、システムにマージされたすべてのプル リクエストのレポートを提供します。
バグ、脆弱性、臭い、およびセキュリティの問題のリストを含むレポートを見ることができます。これらの問題をすべてクリックすると、次のようになります。
問題が問題である理由、修正方法などを正確に説明するタブがあることに注意してください。これは、チームで最も価値のあるコード レビュー担当者の 1 人として機能する非常に強力なツールです。
前に見た 2 つの追加の興味深い要素は、カバレッジ レポートと重複レポートです。 SonarCloud は、テストが 80% のコード カバレッジ (プル リクエストでコードの 80% をトリガーする) を持つことを期待しています。これは高く、設定で構成できます。
また、Don't Repeat Yourself (DRY) 原則に違反している可能性がある重複コードも指摘します。
CI は、プロジェクトの流れを改善する多くの機会を持つ巨大なテーマです。バグの検出を自動化できます。アーティファクトの生成、自動配信などを合理化します。しかし、私の謙虚な意見では、CI の背後にあるコア原則は開発者の経験です。
私たちの生活を楽にするためにここにあります。
それが下手に行われると、CI プロセスはこの素晴らしいツールを悪夢に変える可能性があります。テストに合格することは、無駄な練習になります。最終的にマージできるようになるまで、何度も再試行します。待ち行列が混雑しているため、合流するのに何時間も待ちます。
役立つはずだったこのツールは、私たちの宿敵になります。これはあってはならないことです。 CI は私たちの生活を楽にするものであって、その逆ではありません。