すべてのアプリは、プロジェクトのサービスです。アプリが持つものに加えて、サービスは次のことができます。
- まとめてデプロイするとき、ほかのサービスを待つ:
depends_on。それぞれstarted、ready、completedのいずれか(--depends-on db:ready) - プロジェクトのネットワーク上で別の名前でも見つけられるようにする(
network.aliases、--alias)。その名前は、サービスのアドレス、または各インスタンスのアドレスを返す(network.endpoint_mode:vipまたはdnsrr) - インターネットから切り離す(
public: false)
プライベートネットワーク
各プロジェクトは 10.64.0.0/10 の中に専用のネットワークを持ち、必要に応じて 64 アドレス単位の範囲が割り当てられます。各サービスには、名前を変えても変わらないアドレスが割り当てられ、ネットワーク上の各サンドボックスにもアドレスが割り当てられます。アドレスはどのマシンでも同じで、ネットワークは、プラットフォームのマシン間をつなぐプラットフォーム自身のネットワークを通じて、プロジェクトのサンドボックスとインスタンスにどこで動いていても届きます。プロジェクトのサンドボックスやサービスは、<service>.internal かアドレスでサービスにアクセスします。ネットワーク上のスリープ中のインスタンスは、接続があると復帰します。サービスは、プラットフォームの変数 SBX_SERVICE、SBX_PRIVATE_DOMAIN、SBX_PRIVATE_ADDRESS、SBX_PROJECT で自身の情報を知ることができます。
サービスのアドレスへの新しい接続は、インスタンスが動くマシンによってインスタンス間に振り分けられます。プロジェクト同士が互いのネットワークに到達することはできません。
共有変数と参照
共有変数は、プロジェクトのサービスが使える名前と値です(glac variables put REGION eu、/v1/variables)。1 つのプロジェクトに最大 1,000 個、値はそれぞれ最大 32 KiB です。シークレットと違い、値を読み戻せます。
サービスの環境変数は、共有変数(${{ shared.REGION }})や、ほかのサービスの変数(${{ db.POSTGRES_USER }})を参照できます。参照はバージョンの作成時に値に置き換えられ、バージョンはその値を、アプリは参照を保持します。ほかのサービスのシークレットへの参照は、値全体でなければなりません("${{ db.POSTGRES_PASSWORD }}")。シークレットは名前で渡され、バージョンに書き込まれることはありません。
共有変数を変更すると、バージョンが変わるサービスが再デプロイされます(プロジェクトのデプロイ、cause は variables)。PUT /v1/variables/{name} はそのデプロイの ID(deploy)を返し、変わるバージョンがなければ null を返します。デプロイができない場合(たとえばサービスが互いを循環して待つことになる場合)、変更はデプロイ自身のコードで拒否され、値は書き込まれません。メッセージは、変数が変更されなかったことを伝え、それを参照するサービスを挙げ、それらのデプロイができない理由を、該当する箇所(services.api.env.DATABASE_URL)とともに示します。
プロジェクトのデプロイ
glac deploys create(POST /v1/deploys)は、プロジェクトの複数のサービスをまとめてデプロイします。指定した各サービスは、単独でデプロイした場合と同じものを、今あるものの代わりに受け取り(api=IMAGE なら新しいイメージ、web だけならそのまま)、環境変数でそれらを参照しているサービスも一緒にデプロイされます。
glac deploys create api=ghcr.io/acme/api:2 web
glac deploys ls-f FILE は、サービスを、それぞれバージョンのフィールドを持つ JSON で指定します({"api": {"image": "…", "env": {…}}})。デプロイは、それぞれが待つもの(depends_on と、環境変数が参照するサービス)の順に進みます。
- ステートフルなサービスが最初です。それぞれの唯一のインスタンスが、後述のとおりに置き換えられます。
- 次にジョブ(
kind: job)です。それぞれ、プロジェクトのプライベートネットワーク上のサンドボックスで、最後まで 1 回実行されます。終了コード 0 ならcompletedで、それ以外ならデプロイは失敗します(job_failed、ジョブの最後の数行付き)。サンドボックスの作成から 30 分経っても終わらないジョブはデプロイを失敗させ(job_timed_out)、サンドボックスは削除されます。終了を確認する前にサンドボックスがなくなったジョブも、デプロイを失敗させます(job_lost)。1 回のデプロイでジョブが 2 回実行されることはありません。 - 残りのサービス:それぞれ待つものが終わり次第、バージョンが並行して作られ、まとめてトラフィックを受け取ります。
失敗したデプロイは、失敗した箇所で止まります。それまでに切り替わったものは切り替わったままで、ほかのサービスは元のバージョンのままです。デプロイは、どのサービスがなぜ失敗したかを示します(イベント project.deploy_failed)。プロジェクトで同時に実行されるデプロイは 1 つで、その間に来たデプロイは待機し(queued)、待機中のデプロイは後から来たデプロイに置き換えられ(replaced)、そのサービスは後のデプロイのサービスと一緒にデプロイされます。コントロールプレーンが再起動しても、デプロイは中断したところから続きます。終わったジョブが再実行されることはなく、30 分は開始時点から数え続けます。
glac deploys create はデプロイを待ち、各サービスの進み具合を stderr に表示し、失敗すると 1 で終了します。--no-wait ならすぐに戻り、後から glac deploys get ID --wait で待てます。デプロイ(GET /v1/deploys/{id})は、status(queued、deploying、deployed、failed、replaced)、cause(api、共有変数が変更された variables、取り込みによる compose)、各サービスのステータス、バージョン、待っているもの、そして各ステップにかかった時間(steps)を持ちます。SDK にも同じ呼び出しがあり(Python と TypeScript では glac.deploys)、MCP サーバーには project_deploy と project_deploys があります。
データを保持するサービス
専用のドライブを持つサービスはステートフルです。インスタンスはちょうど 1 つで、削除するまで、ドライブを持つマシン上に、サービスのアドレスで保持されます。
glac drives create --name pgdata --size-bytes 5GiB --region eu
openssl rand -hex 16 | glac secrets put PG_PASSWORD -
glac deploy postgres:16 --name db --port 5432 --region eu --secret POSTGRES_PASSWORD=PG_PASSWORD \
-e PGDATA=/var/lib/postgresql/data/pgdata --drive pgdata:/var/lib/postgresql/data --public false(PGDATA はデータベースをドライブのディレクトリに置きます。新しいファイルシステムの最上位には lost+found があり、PostgreSQL はそこでは起動しないためです。)
- 書き込みは 1 台だけ:このようなサービスのインスタンスは 1 つです。1 以外の
--max-instancesや、キーは拒否されます(409drive_single_writer)。 - リージョンは 1 つ:ドライブはそのリージョンでしかマウントできず、インスタンスはアプリの最初のリージョンに作られます。そのため、上記のように、サービスにはドライブのリージョンを指定します。最初のリージョンが別のリージョンであるバージョンは失敗します(409
not_on_one_machine)。 - 読み書き可能なマウントのみ:バージョンは、まだドライブのスナップショットを読み取り専用でマウントできません(
mode: "snapshot"は 400invalid_fieldで拒否されます)。読み取り専用のマウントはサンドボックス用です。 - 新しいバージョンは、まずサービスのドライブのバックアップを取り(
reason: before_version)、古いインスタンスを止めて、同じドライブ、アドレス、名前で新しいインスタンスを起動します。その間に届いた接続は、拒否されずに最大 10 秒待ちます。新しいバージョンが立ち上がらなければ、同じドライブで以前のバージョンが再び起動されます。 - **準備完了とみなされるタイミング。**イメージ自身の
HEALTHCHECKで判断します。それがなく、プラットフォームが知っているデータベースのイメージなら、そのデータベース自身のコマンドで判断します(PostgreSQL ならpg_isready、MySQL と MariaDB ならmysqladmin ping、Redis、Valkey、KeyDB ならredis-cli ping、MongoDB ならmongoshによる ping)。それ以外は、ポートが接続を受け付けた時点です。いずれも、まずサーバーがループバック以外のアドレスで待ち受けるのを待つため、データベースが初期設定の間だけ動かす一時的なサーバーは数に入りません。そのため、最初のクエリが起動中のデータベースにぶつかることはありません(PostgreSQL の57P03)。 - ドライブから再起動した場合(再起動、またはマシンの復旧)は、準備ができるまで、届いた接続を拒否せずに最大 150 秒保留します。120 秒経ってもチェックに通らない場合はそのまま接続を通し、ログにそう記録されます。
- サーバーが自ら停止した場合(クラッシュ、カーネルの OOM killer、
killによる終了)は、前回の実行が何も残っていない状態になってから、ドライブをそのままにして同じインスタンスの中で再起動されます。0.1 秒後に再起動し、起動から 10 秒以内に再び停止するたびに、前回の 2 倍の時間を空けます(最大 1 分。Docker のrestart: alwaysと同じ数値)。起動のたびにイベントinstance.restartedが、前回の実行の終わり方(exit_status、signal)とともに発生し、インスタンスのstart_command(GET /v1/sandboxes/{id})が、サーバーの動作状況(running、またはrestartingとその時刻)、再起動の回数、最後の終わり方を示します。プラットフォームが求めた停止(削除、再起動、新しいバージョン)はこれに当たりません。通常の(ステートレスな)サービスでは、サーバーが停止したインスタンスは、代わりに別のインスタンスに置き換えられます。 - サンドボックスと同じく中で起きていることに応じてスリープしますが、自身のタイマー(データベースのチェックポイントなど)では稼働し続けません。接続があると復帰します。
- アプリのログには含まれません。出力は
glac sandboxes logs <instance> exe-startで読みます。
データベースは、このようなサービスとそのドライブで、作成と変更は /v1/databases だけで行います。アプリの managed_by({"object": "database", "id": "db-…"})にそう示され、ここでの更新、デプロイ、ロールバック、削除は拒否されます(409 managed_by_database)。ログとメトリクスは、ほかのアプリと同じように読めます。
Compose ファイルの取り込み
glac compose up # compose.yaml in this directory, or the one aboveファイルの各サービスは、同じ名前のプロジェクトのアプリになります。ファイルの各項目は、done、adapted(別の方法で反映。理由付き)、ignored、refused(変更方法付き)のいずれかとして報告され、1 つでも拒否があれば何も書き込まれません。ファイルのシークレットは先にプロジェクトに登録され、ボリュームが作られます。その後、サービスはプロジェクトのデプロイ(cause は compose)としてまとめてデプロイされ、glac compose up はそれを待ちます。同じファイルを再び取り込んでも、何も変わりません。
- ファイルは Docker Compose と同じ方法で見つけます。
-f(-f -なら標準入力から)、なければ環境変数のCOMPOSE_FILE、なければ.envファイル(--env-file、または--project-directoryかカレントディレクトリの.env。COMPOSE_PATH_SEPARATORで分割)から、なければカレントディレクトリかその上位のディレクトリにあるcompose.yaml(またはdocker-compose.ymlなど)です。 --dry-runはレポートを表示するだけです。--strictは、無視されるはずのものを拒否します。- ランタイム設定。
ulimits、sysctls、tmpfs、dns、shm_sizeは、サービスのランタイム設定になり、各インスタンスに適用されます。dnsはadaptedと報告されます。プロジェクトのネットワーク上では、プラットフォームがそのサーバーに代わって問い合わせに応答するためです。 - **無視したものは明示されます。**プラットフォームが適用しない設定(
cap_add、security_opt、logging、pull_policyなど)は、それぞれ理由とともにignoredと報告され、glac compose upは最後の行でそれらをすべて改めて挙げます。network_mode: noneはadaptedです。サービスのエグレスルールが外向きのトラフィックをすべて拒否し、サービスはプロジェクトのネットワーク上に残ります。 - このマシンに Docker Compose がない場合、
glacは Docker Compose v5.6.1 と同じ方法でファイルを自分で読みます(--normalise localでこの読み方を指定できます)。 - **イメージが宣言するボリューム。**イメージがボリュームとして宣言するパス(
VOLUME。WordPress の/var/www/htmlや Redis の/dataなど)に、サービスがボリュームもドライブもマウントしない場合、そこは各インスタンス専用のディスク上のディレクトリになり、新しいバージョンには引き継がれません。バージョンのresolved.unmounted_volumesがそのようなパスを挙げ、glac compose upとglac deployがそれぞれについて警告します。 - プロジェクトにあってファイルにはもうないサービスは残され、レポートで示されます。
--remove-orphansは、デプロイの完了後に、同じ Compose プロジェクトの以前の取り込みがそうしたサービスのために作ったアプリを削除します。 glac compose psはプロジェクトのサービスとそれぞれの状態を一覧表示し、glac compose logsはそれらのログを各行にサービス名を付けてまとめて表示し(-fで追跡)、glac compose restartは各サービスをそのまま再デプロイして、インスタンスを新しく起動します(ステートフルなサービスは、ドライブのバックアップを取ってから、そのドライブで起動)。--buildは、build:を持つサービスをこのマシンの Docker Compose でビルドし、それぞれをimage:が指すレジストリにプッシュしてから取り込みます。そこから作られるバージョンは、イメージをダイジェストで保持します。image:が以前と同じタグを指すサービスは変更なしとみなされ、新しいバージョンは作られません。ビルドごとに別のタグを付けてください。glac compose downは、取り込みで作られたアプリを削除します。ボリュームとシークレットは残ります。- この方法で取り込めないファイル(イメージのないビルド、ホストのパス、
network_mode: service:…)は、Docker を使ってプロジェクトの 1 つのサンドボックスで丸ごと動かせます(glac compose up --one-sandbox)。その場合、サービスは 1 つのサンドボックスとして、まとめて復帰し、スリープし、課金されます。