こんにちは、末廣です。 「CodeBuild サンドボックスを使用したビルドのデバッグ」を試しながら「リモート Docker サーバ」の挙動を確認し、どういった開発をしている場合に使えそうか、料金面に着目して妄想してみました。 それではどうぞ。
関連アップデート
この仕組みを使うと Docker コマンドの実行時(docker build 等)にリモートサーバーを使用するようビルド環境を設定し、複数のビルドにわたって永続的なキャッシュを維持できるようになります。 これにより、一度ビルドしたレイヤーはキャッシュとして保持され、次回以降のビルドが早く完了します。
もともと CodeBuild はローカルキャッシュや、S3 を用いたキャッシュを使うことができました。 しかし、ローカルキャッシュはホストが変わってしまうと使えなくなってしまったり、S3 を用いたキャッシュはS3からのダウンロードと復元が必要なため、キャッシュの量が多い場合に時間がかかってしまうこともありました。
そしてもう一つ、デバッグ作業に役立つアップデートです。
これにより、ビルドのデバッグ作業のためのサンドボックス環境を作成し、Linux コマンド等を使ってファイルを修正しながらビルドエラーを調査することができます。
Docker サーバの設定
公式ドキュメントには AWS CLI でビルドプロジェクトを設定する方法がのっています。
公式ブログにマネジメントコンソールからの設定手順もあります。こちら参考に私もやってみました。
ビルドプロジェクトの作成
AWS CodeBuild が公開している公式 Docker イメージのリポジトリをソースとして使います。(公式ブログ同様)

環境の追加設定の箇所に「Docker server configuration」があるのでチェックを入れ、メモリと CPU の程度を選択します。

Docker サーバの設定ができるのは
- プロビジョニングモデルがオンデマンド
- コンピューティングが EC2
- 実行モードがコンテナ
- OS が Linux(Ubuntu)
あたりの条件があるようです。 例えばコンピューティングを Lambda にした時は Docker サーバのチェック項目がそもそも出てこないのですが、実行モードを「インスタンス」にして作成しようとすると以下のようなエラーが出てプロジェクト作成ができない点、注意が必要です。

ビルドプロジェクトの変更(6/13時点の検証結果
プロジェクト作成後、プロジェクトの詳細タブからこのような感じでみれます。

「環境を編集」からサイズの変更できる?と思いきや、

できませんでした。

どうやら一度 Docker サーバを無効化してから再度有効化する必要があるようで、プロジェクトを作成したときと違うメモリ、CPU タイプのものに変更することもできませんでした。 同じコンピュートタイプであれば 有効 / 無効 を切り替えれるので実際に発生した料金の計算はしていないですが、しばらくキャッシュがいないときは無効にしてしまってもいいかもしれません。
また、ビルドプロジェクトを一度 Docker サーバを有効化せずに作成した後、環境を変更から有効化しようとするとエラーがでるため、アップデート前に作成した既存のプロジェクトに適用するのも難しそうです。

エラーメッセージも奇妙なものが出てるので今後の修正に期待です。
デバッグビルド
サンドボックス環境を作成してデバッグビルドをして中身をみていきます。

マネジメントコンソールからそのままコマンドを打ち込む、SSH クライアントや IDE を使った接続、セッションマネージャーで接続する方法がありますが、今回はコマンドをそのまま打ち込んでいきます。
サンドボックス環境の起動
まず初めに適当なコマンドを実行すると、サンドボックス環境が起動します。

1分くらいで起動終わりました。

このサンドボックスのセッション自体はビルドプロジェクトのタイムアウト時間と同じになっているので、長い時間デバッグ作業する時は注意が必要です。
また、サンドボックスが起動中はプロジェクトの詳細タブ等を押しても見に行けません。デバッグセッションタブしか見れません;;

コマンド叩いてみる
上記で適当に ls
を叩いていたので結果です。
ソースにしたリポジトリのルートにあるディレクトリが見れます。

このように CloudWatch Logs を有効化にしていると、実行コマンド毎にログストリームが作られてログの保存確認も行うことができます。
ビルドする
実際にビルドしていきます。
ビルドプロジェクトの環境を amazonlinux-x86_64-standard:5.0
で作成したので、リポジトリのディレクトリal/x86_64/standard/5.0にある Dockerfile でビルドします。

docker build
します。

ログがでました。"codebuild-docker-server" instance using remote driver
でリモート Docker サーバを使っていることがわかります。
#0 building with "codebuild-docker-server" instance using remote driver #1 [internal] load build definition from Dockerfile #1 transferring dockerfile: 28.44kB done #1 DONE 0.0s #2 [internal] load metadata for public.ecr.aws/amazonlinux/amazonlinux:2023 #2 DONE 1.6s #3 [internal] load .dockerignore #3 transferring context: 2B done ⋮
ちなみに、Docker サーバを有効化せずにビルドすると、
#0 building with "default" instance using docker driver
と出力されます。
Docker サーバの力
なんとこのビルド、失敗しました。公式なのになぜ…
#22 [tools 15/17] RUN set -ex && curl -L "https://download.mozilla.org/?product=firefox-latest&os=linux64" --output /tmp/FirefoxSetup.tar.bz2 && tar xjf /tmp/FirefoxSetup.tar.bz2 -C /opt/ && ln -s /opt/firefox/firefox /usr/local/bin/firefox && rm -rf /tmp/* && firefox --version #22 0.224 + curl -L 'https://download.mozilla.org/?product=firefox-latest&os=linux64' --output /tmp/FirefoxSetup.tar.bz2 #22 0.228 % Total % Received % Xferd Average Speed Time Time Time Current #22 0.230 Dload Upload Total Spent Left Speed #22 0.231 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 100 136 100 136 0 0 336 0 --:--:-- --:--:-- --:--:-- 335 #22 1.375 63 71.1M 63 45.0M 0 0 39.2M 0 0:00:01 0:00:01 --:--:-- 39.2M 100 71.1M 100 71.1M 0 0 45.9M 0 0:00:01 0:00:01 --:--:-- 65.2M #22 1.779 + tar xjf /tmp/FirefoxSetup.tar.bz2 -C /opt/ #22 1.786 bzip2: (stdin) is not a bzip2 file. #22 1.786 tar: Child returned status 2 #22 1.786 tar: Error is not recoverable: exiting now #22 ERROR: process "/bin/sh -c set -ex && curl -L \"https://download.mozilla.org/?product=firefox-latest&os=linux64\" --output /tmp/FirefoxSetup.tar.bz2 && tar xjf /tmp/FirefoxSetup.tar.bz2 -C /opt/ && ln -s /opt/firefox/firefox /usr/local/bin/firefox && rm -rf /tmp/* && firefox --version" did not complete successfully: exit code: 2 ------ > [tools 15/17] RUN set -ex && curl -L "https://download.mozilla.org/?product=firefox-latest&os=linux64" --output /tmp/FirefoxSetup.tar.bz2 && tar xjf /tmp/FirefoxSetup.tar.bz2 -C /opt/ && ln -s /opt/firefox/firefox /usr/local/bin/firefox && rm -rf /tmp/* && firefox --version: 0.224 + curl -L 'https://download.mozilla.org/?product=firefox-latest&os=linux64' --output /tmp/FirefoxSetup.tar.bz2 100 136 100 136 0 0 336 0 --:--:-- --:--:-- --:--:-- 335 100 71.1M 100 71.1M 0 0 45.9M 0 0:00:01 0:00:01 --:--:-- 65.2M 1.779 + tar xjf /tmp/FirefoxSetup.tar.bz2 -C /opt/ 1.786 bzip2: (stdin) is not a bzip2 file. 1.786 tar: Child returned status 2 1.786 tar: Error is not recoverable: exiting now ------ Dockerfile:225 -------------------- 224 | # Install Mozilla Firefox 225 | >>> RUN set -ex \ 226 | >>> && curl -L "https://download.mozilla.org/?product=firefox-latest&os=linux64" --output /tmp/FirefoxSetup.tar.bz2 \ 227 | >>> && tar xjf /tmp/FirefoxSetup.tar.bz2 -C /opt/ \ 228 | >>> && ln -s /opt/firefox/firefox /usr/local/bin/firefox \ 229 | >>> && rm -rf /tmp/* \ 230 | >>> && firefox --version 231 | -------------------- ERROR: failed to solve: process "/bin/sh -c set -ex && curl -L \"https://download.mozilla.org/?product=firefox-latest&os=linux64\" --output /tmp/FirefoxSetup.tar.bz2 && tar xjf /tmp/FirefoxSetup.tar.bz2 -C /opt/ && ln -s /opt/firefox/firefox /usr/local/bin/firefox && rm -rf /tmp/* && firefox --version" did not complete successfully: exit code: 2
原因としては現在、Mozilla が提供する product=firefox-latest&os=linux64 のURLは、.tar.bz2 ファイルではなく、.tar.gz (gzip形式) のファイルをダウンロードするようになっており、bzip2形式 (j) で圧縮されたアーカイブファイルを解凍 (x) することができなくなっているからでした。
そして私はこのエラーを修正している最中にサンドボックスのタイムアウトが発生してしまいました。サンドボックスはデバッグセッション中に永続的なファイルシステムを維持してくれる、すなわち Docker のキャッシュも持ってくれますが、停止するとまたフェーズ21 までのビルド作業を待つ必要があります。
でもこれは Docker サーバを有効化していない時の話です。
再度サンドボックス環境を立ち上げて docker build
すると、キャッシュされています!
#0 building with "codebuild-docker-server" instance using remote driver #1 [internal] load build definition from Dockerfile #1 transferring dockerfile: 28.44kB 0.0s done #1 DONE 0.0s #2 [internal] load metadata for public.ecr.aws/amazonlinux/amazonlinux:2023 #2 DONE 1.0s #3 [internal] load .dockerignore #3 transferring context: 2B done #3 DONE 0.0s #4 [core 1/3] FROM public.ecr.aws/amazonlinux/amazonlinux:2023@sha256:4e1648e3917aea2ecc13ae65ec45a9479380c38a4c525e224aa2d1936f6c9693 #4 resolve public.ecr.aws/amazonlinux/amazonlinux:2023@sha256:4e1648e3917aea2ecc13ae65ec45a9479380c38a4c525e224aa2d1936f6c9693 0.0s done #4 DONE 0.0s #5 [tools 11/17] RUN yum -y install 'dnf-command(config-manager)' && yum config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && yum -y install gh --repo gh-cli #5 CACHED #6 [tools 12/17] RUN set -ex && curl -L https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm --output /tmp/google-chrome-stable_current_x86_64.rpm && yum install -y -q /tmp/google-chrome-stable_current_x86_64.rpm && rm -rf /tmp/* && google-chrome --version #6 CACHED #7 [tools 1/17] RUN wget -nv https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip -O /tmp/samcli.zip && unzip -q /tmp/samcli.zip -d /opt/samcli && /opt/samcli/install --update -i /usr/local/sam-cli -b /usr/local/bin && rm -rf /opt/samcli /tmp/* && sam --version #7 CACHED
今回に限ってはデバッグ作業中のセッション切れによる時間短縮が一度あったのみですが、これが本番ビルドでおこなわれるということです。
Docker サーバを使うと、ローカルキャッシュを使える状態が常にプロビジョニングされたインスタンスで最大 1 か月間残ってくれているという感じですね。
料金と用途
Docker サーバの料金
Docker サーバは秒ごとに料金が発生し、クールダウン期間というものが設定されているようです。
1 秒あたりの永続キャッシュの保存時の料金
Docker イメージサーバーにはクールダウン期間があり、サーバーはアクティブなままで、イメージビルドリクエストの受信のために最大 1 時間待機します。クールダウン期間内にビルドリクエストが届かない場合、サーバーは停止し、保存時のキャッシュ料金が下がります。この機能を無効にするまで、引き続き料金が発生します。
Docker サーバーは秒ごとに料金が発生し、1時間ビルドリクエストが届かないと「クールダウン期間」に入り、料金が安くなります。 0が多すぎるので $1 = ¥140 で単位を整理します。
単位 | 料金(USD) | 料金(日本円) |
---|---|---|
1秒あたり | $0.000000037 | ¥0.00000518 |
1時間あたり | $0.0001332 | ¥0.018648 |
1日(24時間)あたり | $0.0031968 | ¥0.447552 |
1ヶ月(30日)あたり | $0.095904 | ¥13.42656 |
クールダウン期間になっていれば、思ったより料金が発生しないですね。
料金表そのままですが、アクティブな状態での料金は以下です。
アーキテクチャ | メモリ | vCPU | 料金 / 秒 (USD) | 料金 / 時間 (USD) | 料金 / 秒 (円) | 料金 / 時間 (円) |
---|---|---|---|---|---|---|
ARM | 4 GiB | 2 | $0.00004 | $0.144 | ¥0.0056 | ¥20.16 |
x86 | 4 GiB | 2 | $0.00004 | $0.144 | ¥0.0056 | ¥20.16 |
ARM | 8 GiB | 4 | $0.00007 | $0.252 | ¥0.0098 | ¥35.28 |
x86 | 8 GiB | 4 | $0.00008 | $0.288 | ¥0.0112 | ¥40.32 |
ARM | 16 GiB | 8 | $0.00015 | $0.540 | ¥0.0210 | ¥75.60 |
x86 | 16 GiB | 8 | $0.00016 | $0.576 | ¥0.0224 | ¥80.64 |
ARM | 64 GiB | 32 | $0.00059 | $2.124 | ¥0.0826 | ¥297.36 |
x86 | 64 GiB | 32 | $0.00063 | $2.268 | ¥0.0882 | ¥317.52 |
ARM | 128 GiB | 64 | $0.00118 | $4.248 | ¥0.1652 | ¥594.72 |
x86 | 128 GiB | 64 | $0.00126 | $4.536 | ¥0.1764 | ¥635.04 |
一番小さいコンピューティングタイプでもアクティブな時とクールダウン期間の時で 1000 倍程度料金の差があります。
比較して用途を考えてみる
では、どのような時に使えそうでしょうか?これまでのキャッシュ方法と比較します。
比較項目 | Docker サーバー機能 (新機能) | ローカルキャッシュ | S3キャッシュ |
---|---|---|---|
キャッシュの場所 | 永続的なリモートDockerサーバー | ビルドを実行するホストのローカルディスク | ユーザーが指定したS3バケット |
永続性・信頼性 | 非常に高い (最大1ヶ月保持) |
低い(ベストエフォート) (ビルドホストが変わるとキャッシュが消える) |
非常に高い (S3に保存されている限り永続) |
パフォーマンス | 非常に高い (キャッシュヒット率が非常に高く、ネットワーク転送もない) |
非常に高い (キャッシュヒット率が非常に高く、ネットワーク転送もない) |
低い (毎回S3からキャッシュをダウンロード/アップロード、展開するため、遅い) |
料金 | Docker サーバの追加料金がかかる | かからない | S3 への保存料金がかかる (少額) |
料金面を考えなければ、ローカルキャッシュの「不安定さ」とS3 キャッシュの「遅さ」という課題を解決できる優れモノに見えます。 用途を考えると、複数人、または CI/CD で頻繁にビルドが実行されるようなプロジェクトで、ビルド時間をボトルネックにせず、キャッシュを利用した早いビルドを毎回確実に行いたい時とかでしょうか?
料金バランスを考える
Docker サーバを使う時と使わない時、ほとんど料金が変わらないような条件で料金を出してみました。
前提条件
- 休日はビルドしない(Docker サーバは常にクールダウン状態)
- 営業日は Docker サーバは 10時間稼働状態(general1.small: $0.005 / 分)
- 1回のビルドに20分かかる
- キャッシュが
- 1日25回ビルドする
Docker サーバを使わない料金計算
キャッシュヒットが不安定なため、平均ビルド時間を17分とします
- 月間合計ビルド時間:
- 500回 × 17分/回 = 8,500分
- 月額コンピューティング料金:
- USD: 8,500分 × $0.005/分 = $42.50
- 日本円: $42.50 × 140円/ドル = ¥5,950
- 合計月額料金:
- 合計: 約 $42.50 / 5,950円
Docker サーバを使う料金計算
平均ビルド時間を3分に短縮できると仮定します。
コンピューティング料金の計算
月間合計ビルド時間:
- 500回 × 3分/回 = 1,500分
月額コンピューティング料金:
- USD: 1,500分 × $0.005/分 = $7.50
- 日本円: $7.50 × 140円/ドル = ¥1,050
Dockerサーバー機能の料金計算
- 料金単価:
- 稼働時間: $0.00004 / 秒
- クールダウン時間: $0.000000037 / 秒
- 平日1日あたりの料金 (稼働10時間 + クールダウン14時間):
- 稼働料金: $0.00004 × 3,600秒 × 10時間 = $1.44
- クールダウン料金: $0.000000037 × 3,600秒 × 14時間 = $0.0018648
- 小計: $1.4418648 / 日
- 休日1日あたりの料金 (クールダウン24時間):
- クールダウン料金: $0.000000037 × 3,600秒 × 24時間 = $0.0031968 / 日
- 月額のDockerサーバー料金:
- USD: ($1.4418648 × 20日) + ($0.0031968 × 10日) = $28.837... + $0.0319... ≈ $28.87
- 日本円: $28.87 × 140円/ドル ≈ ¥4,042
合計月額料金
- 合計 (USD): $7.50 (コンピューティング) + $28.87 (サーバー) = $36.37
- 合計 (日本円): ¥1,050 (コンピューティング) + ¥4,042 (サーバー) = ¥5,092
- 合計: 約 $36.37 / 5,092円
AI が物語を作りました(参考)
設定: 開発者Aさん、金曜日の朝。新機能「ユーザー推薦機能」の開発を担当。Dockerイメージのビルドには通常20分かかる。
シナリオ1:Dockerサーバーを「使わない」世界(ローカルキャッシュのみ) 午前9:30 Aさんは作業を開始し、最初のコミットをプッシュします。CodeBuildが起動しますが、新しいビルドホストが割り当てられ、キャッシュは効きません。
ビルド時間:20分 Aさんの思考: 「さて、ビルドが終わるまでコーヒーを淹れて、別のタスクの資料でも読んでおくか…」 待ち時間が発生し、集中が一度途切れます。
午前11:00 機能に修正を加え、再度プッシュ。運悪く、また別のホストに割り当てられてしまいました。
ビルド時間:20分 Aさんの思考: 「え、また20分か。午前のうちにプルリクエストを出したかったのに…」 ビルドの「ガチャ」に外れ、計画に遅れが生じ始めます。
午後4:00 コードレビューの指摘を修正し、本日3回目のプッシュ。今度は運良く同じホストにヒットしました。
ビルド時間:3分 Aさんの思考: 「お、速い!ラッキー。よし、すぐ確認してマージしよう」 偶然の幸運に安堵するも、この不安定さが日々のストレスになっています。
【この世界の1ヶ月後】 チームはビルドの遅延に振り回され、開発者の貴重な時間が待ち時間に消えていきました。この不安定な開発体験にかかっているCodeBuildの合計コストは、月額 約5,950円 でした。
シナリオ2:Dockerサーバーを「使う」世界 午前9:30 Aさんは作業を開始し、最初のコミットをプッシュ。これはチームでその日最初のビルドなので、Dockerサーバーのキャッシュはまだウォームアップされておらず、時間がかかります。
ビルド時間:20分 Aさんの思考: 「よし、朝イチのビルドはこんなもんだな。これでチーム全員のキャッシュが温まったはず」 1日の最初の「投資」と割り切り、 predictable な待ち時間として受け入れています。
午前11:00 機能に修正を加え、再度プッシュ。Dockerサーバーの永続キャッシュが即座に応答します。
ビルド時間:3分 Aさんの思考: 「よし、すぐ終わった。これなら午前中にレビューまで出せるな」 確実な高速フィードバックにより、開発がスムーズに進みます。
午後4:00 コードレビューの指摘を修正し、プッシュ。当然のようにキャッシュが効きます。
ビルド時間:3分 Aさんの思考: 「修正完了。CIもすぐ通ったし、マージ依頼しよう」 ビルドの待ち時間を意識することなく、コーディング作業そのものに集中できています。
【この世界の1ヶ月後】 チームはビルドの待ち時間というストレスから解放され、開発サイクルは劇的に高速化しました。この快適な開発体験を実現しているCodeBuildの合計コストは、月額 約5,100円。ローカルキャッシュのみの場合より安く、かつ圧倒的に快適で生産性の高い環境が手に入っています。
結論
具体的な料金計算から、Docker サーバーを使って料金の上がり幅が大きくなってしまう条件も見えてきました。 それは、「Dockerレイヤーのキャッシュがあまり効かない」状況で、かつ「毎日ビルドが実行され、サーバーが常にアクティブ状態になる」場合です。この場合、ビルド時間の短縮メリットが得られないままサーバーのアクティブ料金がかさむため、導入によるコストの上がり幅が最も大きくなると考えられます。このようなケースでは、導入は慎重に検討した方が良いでしょう。
逆に、私が特に効率が良いと感じたのは、「開発を行う日は集中的にビルドする一方、全くビルドしない日もある」というメリハリのある開発スタイルの場合です。 ビルドしない日は安価なクールダウン料金でコストを抑えつつ、ビルドする日には永続的なキャッシュの恩恵を最大限に受けることができるため、費用対効果の高い使い方ができるのではないかと考えました。
おまけ:GitHub Actions (メモ程度)
Github Actions にはそもそも無料枠があるため、そこを超過しない限りは料金が発生しない上に、キャッシュについても無料枠で使うこともできるため、料金を安くしたい場合はそもそも Codebuild を選ぶことが少ないと思います。
無料枠として、Registry cache は 500MB、 GitHub cache は7日間で自動削除などと制限があるようで、実行時間自体も2,000分/月 となっています。
GitHub Action ランナーによって Codebuild を動かすこともできるため、今回の Docker サーバも使うことができますが、すでに無料枠内で使えているようなビルドであればそのままで十分でしょう。
まとめ
本ブログでは「リモート Docker サーバ」の設定方法、挙動を確認し、料金やメリットについて考えてみました。 実際に利用するかどうかは開発フローやキャッシュの使い方、速度の問題と料金を天秤にかけ、検討は必要そうです。 しかし、アクティブなプロジェクトにおいては「簡単に」「思ったより高くなく」「ストレスフリーな Docker ビルド環境」が手に入りそう、というイメージでまとめさせていただこうと思います。
末廣 満希(執筆記事の一覧)
2022年新卒入社です。ここに何かかっこいい一言を書くことができるエンジニアになれるように頑張ります。