Docker PR

【FastAPI入門をやってみた】Docker,PoetryでのFastAPI環境構築を理解する!

【FastAPI入門をやってみた】Docker,PoetryでのFastAPI環境構築を理解する!
記事内に商品プロモーションを含む場合があります

こんにちは!٩( ‘ω’ )و

都内の受託中心に開発を行なっている企業で、Webエンジニアをしているモリヤス(@_moriyas)です!

次のお仕事でバックエンドでPythonFastAPIを使っていくことになったので、入門してみようということで下記の記事をやっていこうと思います!

FastAPI入門

その中でこの記事では、01〜05の内容を進めていきます!

主にFastAPIの環境構築周りの話で、そのまま読み進めて手を動かせばもちろんうまくいきのですが、なんとなくでしか理解できない部分(主にDockerfileの部分)があったので、詳細に調べて、自分なりの理解を書いていきたいと思います。

※この記事の内容は基本的には上記記事の内容を進めていきます(コードを変えたりしている部分はあります。)
※何か間違いなどあれば、教えていただけると幸いですm(_ _)m

PythonとFastAPIの環境構築

まず、適当な場所に今回のプロジェクトディレクトリを作成します。

Terminal
$ mkdir fast-api-intro
$ cd fast-api-intro

続いて、上記で作ったディレクトリの直下にdocker-compose.yamlDockerfileを作成します。

※ここではDockerを使って環境構築を行うので、入っていない方は、Dockerを入れておいてください。

docker-compose.yaml
version: "3"
services:
  fast-api-app:
    build: .
    volumes:
      - .dockervenv:/src/.venv
      - .:/src
    ports:
      - 8000:8000

Dockerfile
FROM python:3.12.2

WORKDIR /src

RUN pip install poetry

COPY pyproject.toml* poetry.lock* ./

RUN poetry config virtualenvs.in-project true
RUN if [ -f pyproject.toml ]; then poetry install --no-root; fi

ENTRYPOINT [ "poetry", "run", "uvicorn", "api.main:app", "--host", "0.0.0.0", "--reload" ]

Dockerfileがどんなことをやっているのか、詳しくみていきます。

FROM python:3.12.2

FROMはイメージを作る際のベースイメージを指定する記述です。

なので、「このDockerfileで作るイメージはpython:3.12.2という既に存在するイメージをベースにしますよ〜」

という宣言になります。

試しに、docker hubで本当にPython:3.12.2というイメージが存在するのか確認してみました!

あった!d( ̄  ̄)

なお、ここでは記事執筆現在(2024年2月)Pythonの最新の安定版だった、3.12.2を選択しました。

WORKDIR /src

WORKDIRDockerfile内でWORKDIR以降の処理を行う作業ディレクトリを指定する記述です。(作業ディレクトリがなければ作成してくれる)

つまり今回の場合は、5行目以降のRUN pip install poetryやそれより下の処理を/srcディレクトリで行うよ」

という宣言になります。

RUN pip install poetry

RUNは、現在のイメージよりも上にある新しいレイヤでコマンドを実行し、その結果を コミット(確定)commitします。

「イメージよりも上にある新しいレイヤでコマンド実行し…」という部分がわからなかったので、調べたところ、下記記事の内容で理解できた気がします。

Dockerコンテナのレイヤ構造とは?

かなり簡潔な説明にすると、RUNはコマンド実行する記述という認識でOKだと思います。

なので、今回は、『pip install poetry』を(/srcディレクトリで)実行するよ〜」

という宣言になります。

なお、poetryとはPythonのパッケージ管理ツールです。僕自身Pythonを初めて使うので詳しくはわかりませんが、他にも色々な管理ツールがある中でpoetryが今時(?)らしいです(詳しくは公式や下記のQiitaの記事がよくまとまっていたので、ご覧ください)

Poetry – Python dependency management and packaging made easy

Poetryをサクッと使い始めてみる

また、poetryをインストールする際に使っているpipPythonパッケージ管理ツールとのことですが、上記の理由からpoetryをインストールして使っていきます。

え?pipはインストールをしていないのに、なんで使えるの?と思ったそこの君(僕もそうでした)

Python3.4以降には標準で付属しているとのことでした(参考→「pip: Python環境構築ガイド – python.jp」)

COPY pyproject.toml* poetry.lock* ./

COPYは、イメージのファイルシステム内の指定先に、ホスト(ローカル)にあるファイルやディレクトリを追加する記述です。

つまり、今回は「ローカルの/fast-api-introディレクトリの直下にある『pyproject.toml』または『poetry.lock』で始まるファイルすべてをイメージのファイルシステム内srcディレクトリ直下に追加するよ〜」

という宣言になります。

なお、pyproject.tomlpoetry.lockpoetryで管理しているパッケージなどを記述するファイルなので、poetryを使う際に必要になってくるファイルです。(詳しくは上記で紹介した公式サイトやQiitaの記事をご覧ください)

RUN poetry config virtualenvs.in-project true

RUNは先述した通り

poetry config virtualenvs.in-project trueはプロジェクトのルートディレクトリ内(つまり今回は/fast-api-introの直下)に仮想環境を作成するような設定です。

上記って何が良いの?と思ったので、色々調べた結果、下記の内容がメリットな気がしております。(先述したとおり、Pythonを初めて使うので、使い勝手がイマイチわからずすみません。。。^^;)

  • 複数のproject があっても、フォルダを移動する事で環境を切り替えられること
  • VSCode (等の IDE) がインストールしたlibraryを認識してくれるようになること

参考:poetry を使って python の project を作る

※複数のPythonプロジェクトを作らないので、1つ目の利点は、効果を感じられないと思っております。

RUN if [ -f pyproject.toml ]; then poetry install –no-root; fi

RUNは先述した通り

if [ -f pyproject.toml ]; then poetry install –no-root; fiDockerfileの記述ではなく、シェルスクリプトです!

Dockerfile独自のifの構文はないみたいですし、あまりにも複雑になる条件分岐は推奨されていないようです。(参考→「DockerFileにif文(条件分岐)」)

シェルスクリプトの内容としては、

pyproject.tomlファイルが存在する場合に、poetry install –no-rootというコマンドを実行する

ということです。

poetry installpyproject.tomlファイルを読み込み、ファイルに書いてあるパッケージの依存関係を解決し、インストールします。

–no-rootのオプションに関しては公式ドキュメントには下記の通りに書いてありました。

By default poetry will install your project’s package every time you run install

If you want to skip this installation, use the --no-root option.

デフォルトでは、poetryはinstallを実行するたびにプロジェクトのパッケージをインストールします。

このインストールをスキップしたい場合は、–no-root オプションを使用してください。

Commands | Documentation | Poetry – Python dependency management and packaging made easy

ちょっと、僕も上記は意味が良くはわかっていません。(「既にインストールしているパッケージも再度インストールしてしまうからそれを防ぐためのオプション」という意味なのかな?)

ENTRYPOINT [ “poetry”, “run”, “uvicorn”, “api.main:app”, “–host”, “0.0.0.0”, “–reload” ]

ENTRYPOINTは、コンテナの実行時にデフォルトで実行するコマンドと引数を設定できる記述です。

つまり、今回作ったイメージを使ってコンテナを実行する際に、

コンテナ内でpoetry run uvicorn api.main:app –host 0.0.0.0 –reloadというコマンドが実行するよ〜

という宣言になります。

poetry run uvicorn api.main:app –host 0.0.0.0 –reloadに関してはここでは、これから作っていくPythonアプリを実行するコマンドという認識でOKです。

これでDockerfileの内容に関してはクリアになったと思います!(そう願う)

続いて、上記docker-compose.yamlDockerfileを使ってDockerイメージを作っていきます。

docker-compose buildを実行すると下記のような感じになるかと思います。

Terminal
$ docker-compose build

[+] Building 4.6s (11/11) FINISHED
=> [fast-api-app internal] load build definition from Dockerfile                                                                      0.0s
=> => transferring dockerfile: 336B                                                                                                   0.0s
=> [fast-api-app internal] load .dockerignore                                                                                         0.0s
=> => transferring context: 2B                                                                                                        0.0s
=> [fast-api-app internal] load metadata for docker.io/library/python:3.12.2                                                          4.5s
=> [fast-api-app 1/6] FROM docker.io/library/python:3.12.2@sha256:eddb404b523afd920cb1bc31ff7309f6375898baf506ab2bb1e31357da693426    0.0s
=> => resolve docker.io/library/python:3.12.2@sha256:eddb404b523afd920cb1bc31ff7309f6375898baf506ab2bb1e31357da693426                 0.0s
=> [fast-api-app internal] load build context                                                                                         0.1s
=> => transferring context: 68B                                                                                                       0.0s
=> CACHED [fast-api-app 2/6] WORKDIR /src                                                                                             0.0s
=> CACHED [fast-api-app 3/6] RUN pip install poetry                                                                                   0.0s
=> CACHED [fast-api-app 4/6] COPY pyproject.toml* poetry.lock* ./                                                                     0.0s
=> CACHED [fast-api-app 5/6] RUN poetry config virtualenvs.in-project true                                                            0.0s
=> CACHED [fast-api-app 6/6] RUN if [ -f pyproject.toml ]; then poetry install --no-root; fi                                          0.0s
=> [fast-api-app] exporting to image                                                                                                  0.0s
=> => exporting layers                                                                                                                0.0s
=> => writing image sha256:4ce555d67d33715ade5c5881ff925b815efc95f3a45bb4fce8c119c752b08b63                                           0.0s
=> => naming to docker.io/library/fast-api-intro-fast-api-app                                                                         0.0s

$

これで上記docker-compose.yamlDockerfileを元にDockerイメージ作成されます。

試しにdocker imagesコマンドでイメージを確認してみます。

Zsh
$ docker images       

REPOSITORY                             TAG       IMAGE ID       CREATED        SIZE
fast-api-intro-fast-api-app           latest    4ce555d67d33   47 hours ago   1.17GB
...
...
...

ディレクトリの名前「fast-api-intro」とdocker-compose.yamlでサービス名として設定した「fast-api-app」が合わさった「fast-api-intro-fast-api-app」というなんとも長ったらしい名前のDockerイメージが作成されていますね!^^;

まぁここではこれでOKですw

FastAPIと実行環境のインストール

FastAPIUvicornというPythonアプリを実行するために必要なパッケージをインストールしていきます。

以下のコマンドを実行します。

Terminal
$ docker-compose run \
  --entrypoint "poetry init \
    --name fast-api-app  \
    --dependency fastapi \
    --dependency uvicorn[standard]" \
    fast-api-app

上記コマンドを簡単に解説すると下記の通り

  • docker-compose run:イメージの構築から、コンテナの構築・起動までやる(ここではイメージを既に作っているから、コンテナの構築と起動までやっている気がする。。。)
  • –entrypoint:「docker-compose run」の引数、コンテナの実行時にデフォルトで実行するコマンドと引数を設定できる。(ここではDockerfileの「ENTRYPOINT [ “poetry”, “run”, “uvicorn”, “api.main:app”, “–host”, “0.0.0.0”, “–reload” ]」の記述を上書きすることになる。)
  • poetry init:pyproject.tomlファイルをターミナル上で色々質問されながら作成するコマンドです。つまり、プロジェクトをpoetryで管理するための初期化というような認識でOKだと思います!
    • –name fast-api-app:poetry initコマンドの「–name」というオプションで、パッケージ名を決めるオプション。(ここではパッケージ名を「fast-api-app」にするという指定)
    • –dependency fastapi:poetry initコマンドの「–dependency」というオプションで、プロジェクトに必要なパッケージを設定するオプション。(ここではfastapiというパッケージをpyproject.tomlファイルの[tool.poetry.dependencies]に記入されることになる)
    • –dependency uvicorn[standard]:上記と同じ

コマンドを実行すると下記のようにターミナル上で色々質問されますが、「Author [None, n to skip]:」の質問だけnを入力してEnterを押して、残りの質問はここでは全てEnterキーを押して飛ばしてしまってOKです。

Terminal
[+] Building 0.0s (0/0)
[+] Building 0.0s (0/0)

This command will guide you through creating your pyproject.toml config.

Version [0.1.0]:
Description []:
Author [None, n to skip]:  n
License []:
Compatible Python versions [^3.12]:

Using version ^0.109.2 for fastapi
Using version ^0.27.1 for uvicorn
Would you like to define your main dependencies interactively? (yes/no) [yes] yes
You can specify a package in the following forms:
  - A single name (requests): this will search for matches on PyPI
  - A name and a constraint (requests@^2.23.0)
  - A git url (git+https://github.com/python-poetry/poetry.git)
  - A git url with a revision (git+https://github.com/python-poetry/poetry.git#develop)
  - A file path (../my-package/my-package.whl)
  - A directory (../my-package/)
  - A url (https://example.com/packages/my-package-0.1.0.tar.gz)

Package to add or search for (leave blank to skip):

Would you like to define your development dependencies interactively? (yes/no) [yes]
Package to add or search for (leave blank to skip):

Generated file

[tool.poetry]
name = "fast-api-app"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.12"
fastapi = "^0.109.2"
uvicorn = {extras = ["standard"], version = "^0.27.1"}


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"


Do you confirm generation? (yes/no) [yes]

$

上記までで、今回作った/fast-api-introディレクトリの直下にpyproject.tomlファイルが追加され、下記のような内容であればOKです。(実行時のバージョンによって変わる可能性はあるはず)

pyproject.toml
[tool.poetry]
name = "fast-api-app"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.12"
fastapi = "^0.109.2"
uvicorn = {extras = ["standard"], version = "^0.27.1"}


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

poetry initした時のオプションで設定したように、name = “fast-api-app”[tool.poetry.dependencies]fastapiuvicornの記述が確認できますね!

それでは続いて、pyproject.tomlファイルを元に、プロジェクトで使うパッケージをインストールしていきます。

下記コマンドを実行します。

Terminal
docker-compose run --entrypoint "poetry install --no-root" fast-api-app

上記コマンドは

fast-api-appコンテナを実行して、コンテナ内でpoetry install –no-rootコマンドを実行するよ〜

という記述になります。(コマンドのそれぞれの内容の詳しい解説は先述しているのでここでは省きます)

上記コマンド実行後、/fast-api-introディレクトリの直下にpoetry.lockファイルが作成されていればOKです。

コンテナ内でpoetry install –no-rootをしているのになぜローカルにpoetry.lockファイルが作成されるの?

そう思ったのですが、これはおそらくdocker-compose.yaml

docker-compose.yaml
    volumes:
      - .dockervenv:/src/.venv
      - .:/src

という記述をしており、コンテナ内の/srcディレクトリ直下ローカルの/fast-api-intro直下volumesの記述でマウントされるためかと思います!

つまり、コンテナ内の/srcにpoetry.lockが作成されるため、それがローカルの/fast-api-intro直下に反映されたということです。

poetry initpyproject.tomlファイルがローカルに作成されるのも同じ原理かと!

先述した通り、ここまででローカルの/fast-api-introの直下にpyproject.tomlpoetry.lockが作成されたので、Dockerイメージを最初から作る場合はFastAPIを含んだPython環境をイメージの中に含めることができるようになっています!

?????

つまり、現状の/fast-api-introのプロジェクトを他の誰かに渡した場合、その人は/fast-api-intro直下で

Terminal
$ docker-compose build

をするだけで、FastAPIを含んだPython環境をイメージを作れる!という理解で良いと思います!(例えばチーム開発で役立ちそう?)

終わりに

ここまででDockerコンテナ内でFastAPIのアプリを動かせるような環境ができているはずです!

次回以降は、FastAPI入門の06以降を進めていきたいと思います!(FastAPIをガツガツ学んでいけるはず٩( ‘ω’ )و)←ブログにはまとめないことにしました!

参考

FastAPI入門

Docker で開発 — Docker-docs-ja 24.0 ドキュメント

docker-compose.ymlの書き方について解説してみた

Poetry – Python dependency management and packaging made easy