Fragments of verbose memory

冗長な記憶の断片 - Web技術のメモをほぼ毎日更新(準備中)

Jan 8, 2025 - 日記

Dockerコンテナをone-off実行するAPI OneOffDockerRunner

Dockerコンテナで “ちょっとした処理” を “単発のスクリプト実行” をしたい場面があり、簡単なAPIを開発しました。 OneOffDockerRunner は、FastAPI + Docker SDK(Docker-Py)を使って開発された、一度きりのDockerコンテナ実行をサクッとREST APIから行えるソフトウェアです。

たとえば「イメージを指定してコマンド1回だけ実行したい」「イメージを都度pullして認証が必要なプライベートリポジトリにも対応したい」などのニーズに応えます。 今回は、このツールの機能・使い方をざっくり紹介します。

特徴

  1. 一度きりのコンテナ実行

    • 任意のDockerイメージ・コマンドを指定して、コンテナを1回だけ起動・実行し、終了後は削除します。
    • docker run --rm ... に相当するようなイメージです。
  2. コンテナイメージを自動的にpullしてくれる

    • APIリクエスト内で指定したDockerイメージは、デフォルトで pull_policy が「always」に設定されています。
    • そのため、既にローカルにあるイメージでも最新化したい場合や、プライベートレジストリの認証情報を付与してpullしたい場合などにも対応可能。
    • 必要に応じて "pull_policy": "never" を設定することで、ローカルにある既存のイメージのみを使うこともできます。
  3. プライベートレジストリへの対応

    • Docker Hub、Google Container Registry (GCR)、GitHub Container Registry (GHCR) など、Dockerイメージのプライベートレジストリ認証に対応しています。
  4. 環境変数やエントリポイントの指定

    • env_vars を渡すことで、実行時に任意の環境変数を簡単に注入できます。
    • entrypoint を指定すれば、デフォルトのエントリポイントを上書きできます。
  5. 出力(stdout・stderr)をAPIレスポンスで取得

    • コンテナ実行後の標準出力・標準エラーをまとめてJSONレスポンスで返してくれます。
  6. Base64エンコード済みファイルやディレクトリをコンテナ内に配置可能

    • REST APIのリクエストボディにBase64で圧縮したディレクトリやファイルを埋め込み、コンテナ起動前にコンテナ内に展開できます。
    • 実行後にファイルやディレクトリをBase64で回収することも可能です。
  7. Docker Volume作成API

    • ターゲットディレクトリを .tar.gz 圧縮 & Base64エンコードして送ると、新規ボリュームにファイル群を展開できます。

使用方法の概要

1. Dockerコンテナとして実行する場合

   docker run --rm \
     -p 8222:8000 \
     -v /var/run/docker.sock:/var/run/docker.sock \
     ghcr.io/tumf/oneoff-docker-runner

このコマンドでホストのポート8222とコンテナのポート8000をバインドし、さらにDockerデーモンのソケットをボリュームマウントします。 こうすることでAPIを通じてコンテナを起動・削除できるわけです。

2. コンテナ実行API(POST /run)を叩く

一例として、 curl/run エンドポイントにリクエストを送ってみます:

   curl -X 'POST' \
     'http://0.0.0.0:8222/run' \
     -H 'accept: application/json' \
     -H 'Content-Type: application/json' \
     -d '{
       "image": "alpine:latest",
       "command": [
         "/test.sh"
       ],
       "volumes": {
         "/app/data": {
           "content": "H4sIAGq4eGYAA0tJLEnUZ6AtMDAwMDc1VQDTZhDaw...",
           "response": true,
           "type": "directory"
         },
         "/test.sh:ro": {
           "mode" : "0755",
           "content": "IyEvYmluL2FzaAoKZWNobyAiSGVsbG8sIFdvcmxkISIgPiAvYXBwL2RhdGEvdGVzdC50ZXh0",
           "type": "file"
         }
       }
     }'
  • image : 実行したいDockerイメージ

  • command : 実行コマンド ( ["/test.sh"] など配列 or 文字列で指定可能)

  • volumes : コンテナ内 /test.sh/app/data ディレクトリにファイルを差し込む例 (volumeの使い方は少し複雑なのでこのあとの章で説明します)

    重要なポイント:

  • OneOffDockerRunnerではデフォルトで「pull_policy」が「always」となっており、コマンド実行時にコンテナレジストリから自動的に最新のイメージをpullします。

  • もし既にローカルにあるイメージのみを使いたい場合は、以下のように "pull_policy": "never" を指定してください。

   {
       "image": "alpine:latest",
       "command": ["echo", "Hello, World!"],
       "pull_policy": "never"
   }

3. プライベートレジストリにアクセスしたいとき

   {
     "image": "ghcr.io/your-username/your-private-image:latest",
     "auth_config": {
       "username": "your-github-username",
       "password": "your-personal-access-token",
       "serveraddress": "https://ghcr.io"
     }
   }

このように auth_config を渡すことで、イメージをpull時にレジストリ認証を行えます。Docker HubやGCR、GHCRなど、各種レジストリで利用可能です。


volumes の使い方詳説

1. /run の volumes 引数

OneOffDockerRunner の /run エンドポイントでは、下記のような JSON で volumes を指定できます。

{
  "image": "alpine:latest",
  "command": ["/test.sh"],
  "volumes": {
    "/test.sh:ro": {
      "type": "file",
      "content": "<base64エンコードしたファイル内容>",
      "mode": "0755"
    },
    "/app/data": {
      "type": "directory",
      "content": "<base64エンコードした.tar.gz>",
      "response": true
    }
  }
}

volumes のキー

  • キー部分: "コンテナ内のパス(:マウントモード)" の形式
    • :ro(read-only)や :rw(read-write)などのモードを指定できます(未指定の場合は rw がデフォルト)。
    • 例: "/test.sh:ro", "/app/data" など

volumes の値 (VolumeConfig)

  • type
    • "file": 1つのファイルとして扱う
    • "directory": ディレクトリとして扱う(tar.gzを解凍して配置)
    • "volume": 既存 or 新規の “Named Volume” をマウント(※詳細は後述)
  • content
    • type が "file" の場合は、ファイルそのものを base64 エンコードした文字列
    • type が "directory" の場合は、.tar.gz に圧縮したディレクトリを base64 エンコードした文字列
  • mode (オプション)
    • ファイルに設定するパーミッション(8進数文字列)
    • "0755", "0644" など。ディレクトリの場合は基本的に無視されます。
  • response (オプション)
    • true にすると、コンテナ終了後にそのファイル/ディレクトリを再度 .tar.gz 化 & base64 エンコードして、APIレスポンスに含めて返却
    • コンテナの中で変更・生成されたファイルを取得したい場合に便利です。
  • name (オプション, type=“volume” のとき)
    • “Named Volume” を使いたいときの名前。既に存在する named volume も、その名前があれば使われます。

2. ファイルのマウント例

まずはファイルを1つだけコンテナに配置したいときの例です。 /run リクエスト:

curl -X POST http://localhost:8222/run \
  -H "Content-Type: application/json" \
  -d '{
    "image": "alpine:latest",
    "command": ["/app/hello.sh"],
    "volumes": {
      "/app/hello.sh:ro": {
        "type": "file",
        "content": "IyEvYmluL2FzaAoKZWNobyAiSGVsbG8gZnJvbSBmaWxlISIK",
        "mode": "0755"
      }
    }
  }'
  • /app/hello.sh というファイルを、base64 エンコードしたシェルスクリプトとしてコンテナ内に配置。
  • モード 0755 を設定して実行可能にしているため、command"/app/hello.sh" を指定し、そのファイルを直接実行。

実行が完了すると、APIレスポンスには実行結果の stdout / stderr が返却されます(例: {"stdout":"Hello from file!\n","stderr":""...} )。


3. ディレクトリのマウント例

続いて、ディレクトリを展開したい場合です。 .tar.gz にまとめたディレクトリを base64 化して content に指定します。 /run リクエスト:

curl -X POST http://localhost:8222/run \
  -H "Content-Type: application/json" \
  -d '{
    "image": "alpine:latest",
    "command": ["ls", "-l", "/app"],
    "volumes": {
      "/app": {
        "type": "directory",
        "content": "H4sIAAAAAAAAA2NgYGBg....<省略>....AAAA",
        "response": true
      }
    }
  }'
  • "type": "directory" を指定すると、.tar.gz を一旦 /tmp に展開して、そのディレクトリ一式をコンテナにマウントします。
  • response: true に設定すると、コンテナ終了後に /app 下の内容を .tar.gz + base64 でレスポンスとして返却。 コンテナ内で生成したデータを簡単にAPI経由でダウンロード可能です。

4. Named Volume (type=“volume”)

4.1. /run で named volume を利用

{
  "image": "alpine:latest",
  "command": ["echo", "test volume"],
  "volumes": {
    "/data": {
      "type": "volume",
      "name": "my-shared-volume"
    }
  }
}
  • type="volume" & name="my-shared-volume" を指定すると、DockerのNamed Volume「my-shared-volume」を /data にマウントします。
  • 既に存在するNamed Volumeなら再利用、新規の場合はDockerが自動で作成します。
  • response: trueサポートしていません(内部実装的には無視されます)。Named Volume はコンテナ終了後もホストにデータが残り続けるため、一時的なファイル抽出とは少し性質が異なるからです。

4.2. /volume エンドポイントで named volume を作成

OneOffDockerRunner には /volume というエンドポイントもあります。 これにより、先に named volume を作成し、中にファイル一式を展開しておくことが可能です。

curl -X POST http://localhost:8222/volume \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-volume",
    "content": "H4sIAIQOfmYAA+2TMQ7DIAxF...<base64圧縮ディレクトリ>...A==",
    "driver": "local",
    "driver_opts": {},
    "labels": {"created_by": "my-service"}
  }'

これで「my-volume」という Named Volume を作成し、base64 で圧縮されたファイル/ディレクトリを展開しておきます。

  • content: tar.gz ディレクトリをbase64エンコードした文字列
  • driver (オプション): デフォルトは local
  • driver_opts / labels (オプション): volume 作成時に付与するオプションやラベル

4.3. 作成した named volume を /run でマウント

上で作成した named volume を /run でもう一度使用する場合は、次のように指定します:

{
  "image": "alpine:latest",
  "volumes": {
    "/mydata": {
      "type": "volume",
      "name": "my-volume"
    }
  },
  "command": ["ls", "/mydata"]
}

こうすることで、事前に /volume エンドポイントで仕込んだファイルが /mydata に含まれた状態でコンテナが起動します。


ソースコード構成・GitHub Actions

このリポジトリでは、以下のディレクトリ/ファイル構成になっています:

oneoff-docker-runner
├── .github
│   └── workflows
│       ├── docker-publish.yml
│       └── python-app.yml
├── Dockerfile
├── main.py
├── healthcheck.py
├── requirements.txt
├── tests
│   └── test_run_container.py
└── ...
  • docker-publish.yml プッシュやタグをトリガーにコンテナイメージをビルドし、GitHub Container Registry (GHCR) へ自動でPushしています。

    • マルチプラットフォーム(linux/amd64, linux/arm64)向けのイメージビルド
    • Buildxを使いキャッシュを効率的に利用
    • cosignを使ったイメージ署名にも対応
  • python-app.yml PythonのユニットテストおよびLint(flake8, pytest)をGitHub Actionsで自動実行し、プルリクエストごとに品質を担保します。Python 3.9 / 3.10 / 3.11のマルチバージョンでテストしています。

  • main.py FastAPIアプリケーションの本体。 /run/volume/health といったエンドポイントが定義されており、Dockerコンテナの実行やVolume作成ロジックが記述されています。

    • pull_policy パラメータで「always/never」を選択するだけで、自動pullのON/OFFを切り替えられるよう設計されています。
  • Dockerfile FastAPI (uvicorn) を起動するためのイメージ定義。 requirements.txt をインストールした上で、 CMD ["uvicorn", "main:app", ...] でアプリを起動します。

  • test_run_container.py fastapi.testclientunittest.mock を使って、コンテナ実行が正しく完了するかをテストします。テスト内では実際のdockerコマンド呼び出しをMock化し、stdout・stderr のテストができるようにしています。


OneOffDockerRunner は、

  • 「とりあえず1回だけコンテナを動かしたい」
  • 「コンテナイメージをpullしながら使いたい(または、すでに手元にあるイメージだけ使いたい)」
  • 「プライベートレジストリからPullして何か一瞬動かしたい」
  • 「API経由で簡単にDockerタスクを実行・管理したい」

といったユースケースにピッタリのシンプルなREST APIサービスです。 ぜひ試してみたい方はGitHubリポジトリをクローンし、ローカルやDockerホストで起動してみてください。Pullリクエスト・Issueも大歓迎です!

Tags: docker

PgBouncerの高可用性構成(HA)

comments powered by Disqus