tech.guitarrapc.cóm

Technical updates

2025年4月以降もGitHub ActionsでUbuntu 20.04環境を継続利用する

GitHub ActionsにはLinuxホストランナーとしてUbuntuがありますが、Ubuntu 20ランナーイメージは2025/02/01-04/15まででサポート終了し、以降はubuntu-20.04ホストランナーを使えません。

とはいえUbuntu 20.04環境をCIとして利用したいケースもあるので、2025/4/16以降もUbuntu 20.04環境を使う方法を紹介します。

なんでUbuntu 20.04環境を使いたいの?

Ubuntu 20.04は2025年4月でEOLを迎え、GitHub Actionsでのランナーイメージ廃止もこれに合わせたようです。EOLを迎える以上、Ubuntu 20.04を動作環境として使うのはやめて22.04や24.04に移行しましょう。しかし、CI環境としてのUbuntu 20.04にはEOLを迎えても使用するメリットがあります。

例えば古いバージョンのglibcやglibc++のバージョンが安定動作するので、多くのLinux環境で動作するネイティブアプリケーションをビルドする環境にはうってつけです。glibcでビルドしたアプリケーションはビルドした環境より新しいglibc環境では互換性があり動作すると期待できる一方、ビルドした環境より古いglibc環境ではまず動作しません。となると、幅広い環境で動作するアプリケーションをビルドするには、古すぎず安定して使える環境のglibcを用いてビルドするのは理にかなっています。

さて、glibcはLinuxディストリビューションごとにバージョンが異なるため、ビルドした環境より新しいglibc環境で動作するかは各ディストリビューションのglibcバージョンに依存します。各ディストリビューションのglibcバージョンは次のサイトでまとまっているので見てみましょう。

  • Ubuntu 20.04: glibc 2.31
  • Ubuntu 22.04: glibc 2.35
  • CentOS Stream 9: glibc 2.34
  • CentOS Stream 10: glibc 2.39

Ubuntu 22.04のglibcは2.35と、結構多くのディストリビューションよりも高いバージョンだと分かります。もちろんUbuntu 22.04のglibcのバージョンを下げることも検討できますが、glibcのバージョンを下げた時に各種ツールチェインが素直に動くかというとそうでもありません。翻ってUbuntu 20.04環境はglibc 2.31と他ディストリビューションよりわずかに古く、また依存が解決された状態です。

Ubuntu 20.04はglibc互換性を考えると最適なビルド環境に見えてきませんか? Ubuntuのglibcバージョン

CentOS Streamのglibcバージョン

これまでのUbuntu 20.04環境の使い方

2025/4/15までは、runs-onubuntu-20.04を指定するだけでUbuntu 20.04をホスト環境として使えました。 例えばこの環境に.NET SDKとRustをインストールするワークフローは次の通りです。

name: ubuntu-20.04
on:
  pull_request:
    branches: [ main ]
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-20.04
    timeout-minutes: 30
    steps:
      - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
        with:
          dotnet-version: '9.0.x'
      - name: check dotnet version
        run: dotnet --list-sdks
      - uses: actions-rust-lang/setup-rust-toolchain@9399c7bb15d4c7d47b27263d024f0a4978346ba4 # v1.11.0
      - name: check rust version
        run: rustc --version
      - name: check cargo version
        run: cargo --version

2025/4/16以降は、ubuntu-20.04を指定してもUbuntu 20.04環境は使えません。

Requested labels: ubuntu-20.04
Job defined at: guitarrapc/ubuntu-glibc/.github/workflows/ubuntu20-old.yaml@refs/pull/2/merge
Waiting for a runner to pick up this job...
Job is waiting for a hosted runner to come online.
Job is completed before starting.

Ubuntu-20.04はもう使えない

2025/4/16以降のUbuntu 20環境の使い方

GitHub ActionsでUbuntu 20.04を使う方法をいくつか考えてみましょう。この中だとBが現実的に手間が小さく、シンプル、何か変更あっても保守しやすそうです。今回はこの方法を考えます。

  • A: GitHub Actionsのジョブ中でDocker ComposeなどでUbuntu 20.04コンテナを起動して実行
  • B: ジョブをUbuntu 20.04コンテナで実行する
  • C: 適当なUbuntu 20.04マシンを用意してセルフホストランナーで実行
  • D: そのほか素晴らしいアイデア

Ubuntuコンテナイメージを選択する

GitHub Actionsのジョブをコンテナで実行する方法1を使うと、GitHub Actionsジョブをホストランナーではなく指定したコンテナで実行できます。

では、どのコンテナイメージを使うと良いでしょうか?ビルドで使うので、gitやcurl、build-essential、ca-certificatesなどは入っていて欲しいです。ホストランナーなら.NET SDKなど各種ランタイムも入っていますが、そこはあきらめるとしてもある程度のセットアップは避けられるなら避けたいものです。ぱっと思いつくのは次の3つです。

  • A: Docker Hubの公式Ubuntuイメージを利用
  • B: 自分でビルドしたイメージをghcrにホストして利用
  • C: 他のイメージを利用

AのDocker Hubの公式Ubuntuイメージには、curlやbuild-essentialを始めとしたツールがほとんど入っていません。ただ、18.04イメージがまだ残っているところを見ると、公式Ubuntuが数年以内に過去のイメージを消す確率は低そうです。

BはAの発展形です。ビルドごとのツールセットアップに時間がかかるなら、事前にイメージをビルドしてghcrにホストしておくと効率的です。ただしDockerfileを用意してイメージを作成し、ghcrにホストするなどのメンテナンスが必要です。

Cは、A/Bの手間を避けたい気持ちが強い選択です。始めから各種aptが入っている3rdパーティのUbuntuイメージがあれば手間を大きく削減出来そうです。3rdパーティということは、Ubuntu 20.04のコンテナイメージがEOLで消されてもしょうがないリスクがあります。

条件をいろいろ検討すると、Aの公式Ubuntuイメージを使ってビルドごとにツールをセットアップで始めれば良さそうです。ツールのセットアップ時間が耐えきれないぐらいに長いなら、Bにしましょう。

方針が決まったのでワークフローを用意します。

ジョブをUbuntu 20.04コンテナで実行する

GitHub Actionsのジョブをubuntu:20.04コンテナで実行するには、runs-on: ubuntu-24.042を指定しcontainerimage:ubuntu:20.04を指定します。後は必要なツールをセットアップして、アプリケーションビルドすればOKです。

name: ubuntu-20.04
on:
  pull_request:
    branches: [ main ]
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-24.04
    container:
      # Needs to lock glibc version to 2.31, ubuntu 20.04 has glibc 2.31
      image: ubuntu:20.04
    timeout-minutes: 30
    steps:
      - name: apt-get
        env:
          DEBIAN_FRONTEND: noninteractive
        run: |
          apt-get update
          apt-get install -y unzip curl git autoconf build-essential ca-certificates tzdata
      # follow https://learn.microsoft.com/en-us/dotnet/core/install/linux-ubuntu-install?tabs=dotnet8&pivots=os-linux-ubuntu-2004#register-the-ubuntu-net-backports-package-repository
      - name: apt-get (.NET)
        run: |
          apt-get install -y libicu66
      - name: check glibc version
        run: ldd --version
      - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
        with:
          dotnet-version: '9.0.x'
      - name: check dotnet version
        run: dotnet --list-sdks
      - uses: actions-rust-lang/setup-rust-toolchain@9399c7bb15d4c7d47b27263d024f0a4978346ba4 # v1.11.0
      - name: check rust version
        run: rustc --version
      - name: check cargo version
        run: cargo --version

ワークフローを実行すると.NET SDKやRustツールチェインがセットアップできているのがわかります。

ワークフローを実行するとツールチェインがセットアップできている

コンテナ実行ジョブの注意点

コンテナ実行ならではの注意点があります。公式ドキュメントでは記載がないので参考にどうぞ。各セクションは次のような意味を持ちます。

  • ✔️ DO: 推奨
  • ⚠️ CONSIDER: 検討/考慮する
  • ❌ AVOID: 避ける

✔️ DO: 公式のイメージセットアップを参考にする

Ubuntu 20.04など指定した環境を再現する場合、公式はどうやっているのか、どうやるのを推奨しているのか参考にするのが良いでしょう。

  • GitHub ActionsのUbuntu 20.04ホストランナーOSはactions/runner-imagesにコンテナイメージセットアップがある
  • .NET SDKのコンテナはdotnet/dotnet-dockerにイメージセットアップがある
  • .NET SDKのUbuntuセットアップはMicrosoft Learn3にUbuntu 20.04向けaptセットアップがある

今回.NET SDK向けにICUだけ導入しましたが、公式ドキュメントは他ツールのセットアップも推奨しています。必要に応じて追加してください。

  • ca-certificates
  • libc6
  • libgcc-s1
  • libgssapi-krb5-2
  • libicu66
  • libssl1.1
  • libstdc++6
  • zlib1g

⚠️ CONSIDER: defaults.run.working-directoryはコンテナに存在するパスか注意する

指定したパスでrunのコマンドを実行するのにdefaults.run.working-directoryを指定できます。コンテナ実行の場合、必ずコンテナに存在するパスを指定してください。

ホストマシンのruns-onの場合、ホストに存在しないパスを指定していても特にエラーが出ることはありませんでしたが、コンテナにおいてはコンテナにないパスを指定するとOCI runtime exec failedエラーが出ます。 なお、nodejsアクションのようにrun:を使っていない場合は、ホストに存在しないパスで実行されてもエラーになりません。

jobs:
  build:
    runs-on: ubuntu-24.04
    container:
      image: ubuntu:20.04 # コンテナジョブ実行ならなんでもいい
    timeout-minutes: 30
    defaults:
      run:
        working-directory: /foo # コンテナに存在しないパスを指定するのは避ける!
    steps:
      - name: apt-get
        run: |
          echo foo
OCI runtime exec failed: exec failed: unable to start container process: chdir to cwd ("/foo") set in config.json failed: no such file or directory: unknown
Error: Process completed with exit code 126.

もしリポジトリにあるパスを指定したい場合は、runが実行される前にactions/checkoutを実行しておくとエラーを回避できます。

jobs:
  build:
    runs-on: ubuntu-24.04
    container:
      image: ubuntu:20.04
    timeout-minutes: 30
    defaults:
      run:
        working-directory: /foo # リポジトリに/fooが含まれる
    steps:
      # 先にチェックアウトする
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - name: apt-get
        run: |
          echo foo

❌ AVOID: 大きなサイズのパッケージは利用を避ける

GitHub Actionsのコンテナ実行に限った話ではありませんが、サイズの小さいパッケージを利用するようにしましょう。パッケージサイズを小さくするだけで、ビルドの安定性が上がります。

毎ビルド時にセットアップするので、パッケージサイズが大きいと失敗する確率が飛躍的に上がります。残念ながらaptはそこまで安定していませんし、.NETインストールスクリプトのホスト先も不安定です。

# AVOID: サイズの大きなパッケージは避ける
apt-get install -y libicu-dev

# DO: サイズの小さなパッケージを利用する
apt-get install -y libicu66

まとめ

glibcのバージョンに由来してUbuntu 20.04環境を使う、そんなニッチなニーズも世の中にはあります。GitHub Actionsは任意のコンテナイメージを利用できるので、小さな努力でやりたいことが実現できるのは素晴らしいですね。

GitHub Actionsのアクションを利用してセットアップを簡略化できるのも魅力的です。

参考


  1. 似た方法にサービスコンテナがありますがサービスコンテナはジョブの実行環境を提供するものではなく、ジョブの実行環境にサービスを追加するものです。
  2. ubuntu-24.04でなくても他のイメージでもいいですが、わかりやすく現在の最新ホストランナーにします
  3. Microsoft Learnの記事は更新される可能性があるので、GitHubのコミットを指しておきます