APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

GithubActionsのrunnerにBlackSmithを使って高速化とコスト削減する

はじめに

こんにちは。クラウド事業部の上野です。

今回はxでBlackSmithという面白そうなTopicを見つけたのでどんなものなのか試してみました。

GithubActionsのコスト削減と高速化に役立ちそうなので興味のある方は見ていただけると幸いです。

BlackSmithとは?

blacksmithはGithub Actionsのランナーをsaasのような形で提供してくれるサービスです。

Github Actionsのruns-onをblacksmithのランナーに置き換えるだけで高速化とコスト削減が可能です。

また、このランナー専用のGUIが用意されておりその画面からGithub Actionsの画面から分析できることと同程度かそれ以上の分析ができます。(個人的には使いやすいGUIかなと)

※ 注意点としてOrganization管理のリポジトリでしか現状は利用できないです。

www.blacksmith.sh

QuickStart

docs.blacksmith.sh

GithubのOrganizationにアプリをインストールする。

Migration Wizardを使用して自動でPR作成してくれる。(以下画像)

Blacksmithを使い始める最も簡単な方法は、移行ウィザードを使用することです。このウィザードは、Blacksmithランナーを使用するために必要なすべての変更を含むPRを3回のクリックで作成します。

Wizardを使わない場合は、手動で全てのruns-onで使用するランナーをblacksmithのものに変更する必要がある。

jobs:
  build:
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-2vcpu-ubuntu-2204

※ Wizardから設定すると勝手にblacksmith-4vcpu-ubuntu-2404が設定されてて、一番安いランナーではないものでPRが作られるので注意。

高速化される仕組み

コロケーションキャッシュを用いた高速化

依存関係を自動でキャッシュするが、ジョブサーバーとキャッシュストレージが物理的に近い場所に配置されたBlackSmithのサーバーを使用して依存関係のキャッシュと解決ジョブの実行を行うためネットワーク遅延を少なくして高速に実行することができる。(コロケーションキャッシュという)

docs.blacksmith.sh

Dockerレイヤーの永続化

docker/build-push-action@v3useblacksmith/build-push-action@v2 に変更すれば、Dockerビルド時のレイヤーは裏側でstickydiskという永続化ストレージに保存され、以降のDockerビルドが最大40倍高速化する。

また、stickydiskは 0.50ドル/1GB/月 かかり無料枠はないが、CIのDocker Buildで多くの時間が消費されている場合、実行時間分を削減することでコスト削減に繋げられたりする。

stickydiskは複数jobを跨いでデータを保持できる。そのためGithubActionsではartifactなどで受け渡しをしないといけない遅い処理を効率化できる。

docs.blacksmith.sh

即実行可能

GithubのrunnerでCI/CDを実行している場合、サイズの大きいランナーを指定している場合リソースが枯渇して起動に時間がかかることがある。

BlackSmithはこれらを解決する。高スペックなリソースは常に即時実行できる状態になっており、上記のようなタイムラグなしでCI/CDを即座に実行開始することができる。

これはAWS Lambdaと同じFirecracker microVMでジョブを実行することで、3秒未満で起動できることで実現している。

docs.blacksmith.sh

高性能なマシンを利用

Githubのrunnerで使用されるサーバー用CPUより高速な、最新のゲーミングCPU群を使用しているらしい。

使用できるストレージ

使用できるストレージはCacheストレージとStickyDiskの2種類ある。

Cacheストレージは、actions/cache@v4やuseblacksmith/setup-go@v6などの裏側で依存関係を保持するのに使用される通常のキャッシュとして使用できる。

docs.blacksmith.sh

以下のような管理画面で使用状況の確認が可能。

StickyDiskはもう少し便利で、jobを跨いでデータを保持できるストレージである。useblacksmith/build-push-action@v2を使ってDockerレイヤーキャッシュの保存先として透過的に使用されるか、useblacksmith/stickydisk@v1 で意図的に使用することができる。

料金は0.50ドル/1GB/月。

jobを跨いだデータの受け渡しをこれによって高速化することができたりする。

同じく以下のような管理画面で確認可能。

料金について

価格については、基本的な1分あたりの料金が単純に半額なことに加え、高速化による実効時間削減により最大75%削減できる。

www.blacksmith.sh

そもそものGithubActionsの料金体系

  • 基本料金
    • プライベートリポジトリは、プランによって無料枠の実行時間とストレージ容量が決まっており、それを超えると課金される。
    • コスト計算式(https://github.com/pricing/calculator?feature=actions
      • 「分単位の実行時間+ストレージ使用料」で計算できる。
      • 標準Linuxランナーで、5分かかる処理を10回/1日。20営業日実行した場合。
      • ($0.008/min × 5min) × 10回/day × 20day = $8
  • 無料枠
    • プランによって無料枠は変わる。
      • GitHub Freeプランなら月2,000分と500MBのストレージが無料枠。
      • 実行時間は毎月リセットされるが容量についてはリセットされない点に注意。
    • より大きなランナーをTeam or Enterpriseプランで使用する場合は無料枠がない。
    • パブリックリポジトリのランナー利用であれば無料。

BlackSmithの料金体系

  • 基本料金
    • 安いもので $0.004/min から。(他サイズのランナーも基本的に料金が通常ランナーに比べて50%安い)
    • ジョブを跨いでデータ保存できるstickydiskは 0.50ドル/1GB/月かかる。
  • 無料枠
    • 毎月 3000 x64 2vCPU minutes の実行時間分が無料で使用できる。
      • つまり2vCPUのランナーでなら3,000minutes無料だが、4vCPUなら1,500minutes無料、8vCPUなら約360minutes無料となる。
    • 週あたり25GBのキャッシュストレージが利用可能。
      • 超過すると古いものから削除。
      • 通常のランナーでは10GBまでしか使えなかったのがかなり増える。これによりキャッシュヒット率も向上する。
    • stickydiskに無料枠はない。

実際に改善されるか試す

docker buildの結果を比較

docker buildをキャッシュなしとキャッシュありで実行した場合の2パターンで検証を行う。

ubuntu-latest

Go Dockerfileの初回実行、2回目実行

PHP Dockerfileの初回実行、2回目実行


項目 Go(no cache) Go(with cache) PHP(no cache) PHP(with cache)
1回目 1m7s 1m49s 2m7s 3m17s
2回目 1m12s 1m43s 2m7s 17s

blacksmith-2vcpu-ubuntu-2404

blacksmithでイメージレイヤーをキャッシュするには使用するactionを以下の通り変更する必要がある。

  • docker/setup-buildx-action@v3useblacksmith/setup-docker-builder@v1
  • docker/build-push-action@v3useblacksmith/build-push-action@v2

(※useblacksmith/build-push-action@v2は裏側でstickyDiskというストレージが使用されるため料金がかかります)

docs.blacksmith.sh

Go Dockerfileの初回実行、2回目実行

PHP Dockerfileの初回実行、2回目実行


項目 Go(no cache) Go(with cache) PHP(no cache) PHP(with cache)
1回目 32s 36s 1m1s 1m15s
2回目 28s 40s 1m5s 18s

PHPのwith cacheの2回目はあまり変わっていないが、それ以外は明らかに改善されていることが分かる。

検証に使用したDockerfile, workflowファイル

今回使用しているDockerfileはあえて時間がかかるDockerfileをAIに生成させて動くように調整しただけのものなので無駄なpackageもあえて色々インストールされています。

Go Dockerfile

```docker
# ビルド時間を長くするためのマルチステージDockerfile
FROM golang:1.21-alpine AS builder

# 必要なパッケージのインストール(コンパイル時間を増加)
RUN apk add --no-cache \
    git \
    gcc \
    musl-dev \
    sqlite-dev \
    postgresql-dev \
    ca-certificates \
    tzdata \
    make \
    bash \
    curl \
    wget

# 作業ディレクトリの設定
WORKDIR /app

# Go modulesファイルとソースコードをコピー
COPY go.mod go.sum* ./
COPY . .

# go.sumファイルが存在しない場合の対応とモジュール整理
RUN go mod tidy

# 依存関係の事前ダウンロード(キャッシュを利用しつつビルド時間を増加)
RUN go mod download

# 依存関係の検証
RUN go mod verify

# アプリケーションのビルド
RUN go build -o main .

# 実行用の最小イメージ
FROM alpine:3.18 AS runtime

# 実行に必要なパッケージをインストール
RUN apk add --no-cache \
    ca-certificates \
    tzdata \
    postgresql-client \
    curl \
    bash

# 非rootユーザーの作成
RUN adduser -D -s /bin/sh appuser

# 作業ディレクトリの設定
WORKDIR /app

# ビルドしたバイナリをコピー
COPY --from=builder /app/main .
COPY --from=builder /app/config.yaml .

# ファイルの権限設定
RUN chown -R appuser:appuser /app
USER appuser

# ポートの公開
EXPOSE 8080

# ヘルスチェックの設定
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

# アプリケーションの実行
CMD ["./main"]

```

Go workflow

```yaml
name: Build and Test Go Application

on:
  pull_request:
    branches: [ main, develop ]
    paths:
      - 'go-project/**'

jobs:
  build-without-cache:
    name: Build Go App (No Cache)
    runs-on: blacksmith-2vcpu-ubuntu-2404 # or ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Build Docker image without cache
      working-directory: ./go-project
      run: |
        echo "Building without Docker cache..."
        docker build --no-cache -t go-heavy-build:no-cache .

  build-with-cache:
    name: Build Go App (With Cache)
    runs-on: blacksmith-2vcpu-ubuntu-2404 # or ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Docker Buildx
      uses: useblacksmith/setup-docker-builder@v1 # or docker/setup-buildx-action@v3

    - name: Build Docker image with cache
      uses: useblacksmith/build-push-action@v2 # or docker/build-push-action@v5
      with:
        context: ./go-project
        push: false
        tags: go-heavy-build:with-cache
        cache-from: type=registry,ref=go-heavy-build:cache
        cache-to: type=inline
        # ubuntu-latestでは以下にしていてそのままだとエラーになりました。
        # cache-from: type=gha
        # cache-to: type=gha,mode=max
```

PHP Dockerfile

```docker
# ビルド時間を長くするためのマルチステージDockerfile for PHP
FROM php:8.2-fpm AS builder

# システムパッケージの更新とビルドツールのインストール(PostgreSQL API用に最適化)
RUN apt-get update && apt-get install -y \
    build-essential \
    git \
    curl \
    # libpng-dev \
    # libjpeg62-turbo-dev \
    # libfreetype6-dev \
    libonig-dev \
    libxml2-dev \
    libzip-dev \
    libpq-dev \
    libicu-dev \
    libssl-dev \
    libcurl4-openssl-dev \
    libedit-dev \
    libsodium-dev \
    libargon2-dev \
    libsqlite3-dev \
    # libmagickwand-dev \
    # libmagickcore-dev \
    # imagemagick \
    # libmemcached-dev \
    zlib1g-dev \
    unzip \
    zip \
    wget \
    # nodejs \
    # npm \
    # yarn \
    # python3 \
    # python3-pip \
    && rm -rf /var/lib/apt/lists/*

# PHP拡張機能のインストール(PostgreSQL API用に最適化)
RUN docker-php-ext-install -j$(nproc) \
        pdo \
        pdo_pgsql \
        mbstring \
        zip \
        intl \
        opcache \
        bcmath
        # pdo_mysql \
        # mysqli \
        # exif \
        # pcntl \
        # gd \
        # soap \
        # sockets \
        # calendar \
        # gettext

# Composerのインストール
COPY --from=composer:2.5 /usr/bin/composer /usr/bin/composer

# 作業ディレクトリの設定
WORKDIR /var/www/html

# composer.jsonをコピー(必須)
COPY composer.json ./

# composer.lockがある場合のみコピー(存在しない場合はスキップ)
COPY composer.lock* ./

# Composerの依存関係インストール(最適化済み)
RUN composer config --no-interaction allow-plugins.* true \
    && composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist

# ソースコードをコピー
COPY . .

# Composerのオートローダー最適化
RUN composer dump-autoload --optimize --no-dev --classmap-authoritative

# PHPの設定ファイル生成段階
FROM builder AS config

# PHP設定ファイルの作成
RUN echo "memory_limit = 512M" >> /usr/local/etc/php/conf.d/custom.ini \
    && echo "upload_max_filesize = 100M" >> /usr/local/etc/php/conf.d/custom.ini \
    && echo "post_max_size = 100M" >> /usr/local/etc/php/conf.d/custom.ini \
    && echo "max_execution_time = 300" >> /usr/local/etc/php/conf.d/custom.ini \
    && echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/opcache.ini \
    && echo "opcache.memory_consumption=128" >> /usr/local/etc/php/conf.d/opcache.ini \
    && echo "opcache.max_accelerated_files=10000" >> /usr/local/etc/php/conf.d/opcache.ini

# 最終的な実行用イメージ
FROM php:8.2-fpm AS runtime

# 必要な実行時パッケージのみをインストール(PostgreSQL API用に最適化)
RUN apt-get update && apt-get install -y \
    # libpng16-16 \
    # libjpeg62-turbo \
    # libfreetype6 \
    libonig5 \
    libxml2 \
    libzip4 \
    libpq5 \
    libicu72 \
    libcurl4 \
    libedit2 \
    libsodium23 \
    libsqlite3-0 \
    # libmagickwand-6.q16-6 \
    # libmagickcore-6.q16-6 \
    # imagemagick \
    # libmemcached11 \
    nginx \
    supervisor \
    && rm -rf /var/lib/apt/lists/*

# PHP拡張機能をコピー
COPY --from=builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
COPY --from=builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/

# アプリケーションファイルをコピー
COPY --from=config /var/www/html /var/www/html
COPY --from=config /usr/bin/composer /usr/bin/composer

# Nginxの設定
COPY docker/nginx.conf /etc/nginx/sites-available/default

# Supervisorの設定
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# 権限設定(存在チェック付きで安全にディレクトリ作成・権限設定)
RUN chown -R www-data:www-data /var/www/html \
    && mkdir -p /var/www/html/storage /var/www/html/bootstrap/cache /var/www/html/logs \
    && chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap /var/www/html/logs \
    && chmod -R 755 /var/www/html/storage /var/www/html/bootstrap/cache /var/www/html/logs

WORKDIR /var/www/html

# ポートの公開
EXPOSE 80 9000

# ヘルスチェック
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost/health || exit 1

# Supervisorでnginxとphp-fpmを起動
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

```

PHP workflow

```yaml
name: Build and Test PHP Application

on:
  pull_request:
    branches: [ main, develop ]
    paths:
      - 'php-project/**'

jobs:
  build-without-cache:
    name: Build PHP App (No Cache)
    runs-on: blacksmith-2vcpu-ubuntu-2404 # or ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Build Docker image without cache
      working-directory: ./php-project
      run: |
        echo "Building PHP application without Docker cache..."
        time docker build --no-cache -t php-heavy-build:no-cache .

  build-with-cache:
    name: Build PHP App (With Cache)
    runs-on: blacksmith-2vcpu-ubuntu-2404 # or ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Docker Buildx
      uses: useblacksmith/setup-docker-builder@v1 # or docker/setup-buildx-action@v3

    - name: Build Docker image with cache
      uses: useblacksmith/build-push-action@v2 # or docker/build-push-action@v5
      with:
        context: ./php-project
        push: false
        tags: php-heavy-build:with-cache
        cache-from: type=registry,ref=go-heavy-build:cache
        cache-to: type=inline
        # ubuntu-latestでは以下にしていてそのままだとエラーになりました。
        # cache-from: type=gha
        # cache-to: type=gha,mode=max
```

まとめ

今回は、BlackSmithというものをxで見かけて、Github ActionsのRunnerにこのようなアプローチによる高速化、効率化のやり方があるのかというところに面白さを感じて試してみました。

実際に試してみたところ速度改善できそうですし、速度の改善がほぼ見られなくてもそもそも料金が半額なので切り替える価値が十分にあると思います。またStickyDiskを使う前提のJob設計に見直すことで高速することも可能ではと思いました。

ただそれなりの規模の企業になればセキュリティ面などが気になるとは思うので、まだその規模ではないような会社であればとりあえず導入してみるのはお勧めできそうです。