EmotionTechテックブログ

株式会社エモーションテックのProduct Teamのメンバーが、日々の取り組みや技術的なことを発信していくブログです。

Pythonプロジェクトにおけるディレクトリ構成と現状の課題について

はじめに

エモーションテックでSREチームに所属しているsugawaraです。 前回書いたブログでは、Pythonプロジェクトで利用しているツールについてご紹介しましたが、 今回はPythonプロジェクトにおけるディレクトリ構成、コンテナのビルドをどのように行なっているか、また現状課題に思っていることについてご紹介します。

ディレクトリ構成について

Poetryを利用していることは前回書いたブログでもご紹介しましたが、 社内の一部Pythonプロジェクトのディレクトリ構成については以下のようなmonorepoになっています。

.
├── .github
│   ├── dependabot.yml
│   └── workflows
│        └── ci.yml
├── README.md
├── env
├── experiment
│   ├── README.md
│   ├── experiment
│   ├── poetry.lock
│   ├── poetry.toml
│   └── pyproject.toml
├── lib
│   ├── README.md
│   ├── lib
│   ├── poetry.lock
│   ├── poetry.toml
│   └── pyproject.toml
├── poetry.lock
├── poetry.toml
├── project1
│   ├── README.md
│   ├── Dockerfile
│   ├── poetry.lock
│   ├── poetry.toml
│   ├── project1
│   └── pyproject.toml
├── project2
│   ├── README.md
│   ├── poetry.lock
│   ├── poetry.toml
│   ├── project2
│   └── pyproject.toml
├── pyproject.toml
└── setup.cfg

rootと各ディレクトリごとにpyproject.tomlを置いています。各ディレクトリについて簡単に説明すると以下になります。

  • experiment: 将来的にlibに追加することになる実験コード置き場
  • lib: experiment, project1, project2で利用する商用コード置き場
  • project1: libを利用する商用コード置き場
  • project2: project1と同様

このようなディレクトリ構成にした経緯ですが、元々はrootにあるpyproject.toml, poetry.lockで全て管理していました。ただ、以下の課題がありました。

  • lib, projectで利用するライブラリとexperimentで利用するライブラリを別々に管理したい
    • 実験でしか利用しないライブラリのバージョンアップや脆弱性対応が必要になるのは大変であるため
    • コンテナ化をする時に、商用コードにとって不要なライブラリが含まれていると、コンテナのビルドにかかる時間(ライブラリのインストール時間)が不必要に長くなるため
  • 商用コードとそれ以外のコードの置き場をぱっと見で把握できるよう整理したい

上記課題を解決するため、以下を参考に現在のmonorepo構成に至っています。 https://github.com/adriangb/python-monorepo/tree/main/poetry

また、リポジトリを分離する案もありましたが、当面は考えないことにしました。理由は以下の通りです。

  • 今のフェーズでは実験コードのつもりのものがすぐに商用に採用されることが頻繁にあり得るため、リポジトリを分けるとどちらでコードを書けば良いか迷うことが考えられる。また、複数のリポジトリのコードをメンテナンスすると更新漏れが容易に発生するため混乱が生じそう
  • リポジトリを分けると、商用リポジトリと実験リポジトリのライブラリの乖離を防いでいくのが大変そう

コンテナのビルド方法について

Poetryを利用してmonorepoを管理すると課題になってくるのが、コンテナのビルド方法です。現状どのように実現しているかご紹介します。 project1はlibに依存しているため、以下のようなDockerfileをプロジェクト配下に格納しています。

# syntax = docker/dockerfile:experimental
ARG PY_VERSION=3.11

FROM python:${PY_VERSION}-bullseye as first-builder

ARG POETRY_VERSION=1.6.1

COPY pyproject.toml poetry.lock /opt/
COPY lib /opt/lib
COPY project1 /opt/project1

WORKDIR /opt

RUN pip install --no-cache-dir poetry==$POETRY_VERSION && \
  poetry export --only project1 --without-hashes --output poetry-requirements.txt

FROM python:${PY_VERSION}-bullseye as builder

COPY --from=first-builder /opt/poetry-requirements.txt /opt/
COPY lib /opt/lib
COPY project1 /opt/project1
WORKDIR /opt

RUN --mount=type=ssh mkdir -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts \
  && pip install --no-cache-dir -r poetry-requirements.txt

FROM python:${PY_VERSION}-slim-bullseye

ENV PYTHONUNBUFFERED 1
ARG PY_VERSION

COPY --from=builder /usr/local/lib/python${PY_VERSION}/site-packages /usr/local/lib/python${PY_VERSION}/site-packages
COPY --from=builder /usr/local/bin/gunicorn /usr/local/bin/gunicorn
COPY --from=builder /usr/local/bin/newrelic-admin /usr/local/bin/newrelic-admin

# install libgeos-c1v5 for shapely
RUN apt-get update && apt-get -y upgrade \
  && apt-get install -y --no-install-recommends \
    libgeos-c1v5 \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

COPY project1 /app/project1
WORKDIR /app/project1/

CMD ["gunicorn", "project1.main:api", "-c", "project1/gunicorn_config.py"]

上記Dockerfileを利用し、rootをbuild contextに指定してマルチステージビルドをしています。

cd ./project1
DOCKER_BUILDKIT=1 docker build -f ./Dockerfile --ssh default ../

また、社内のプライベートリポジトリを利用するため、BuildKitを利用したssh認証をしています。 https://docs.docker.jp/develop/develop-images/build_enhancements.html

課題に思っていること

lockファイルのメンテナンスが大変

lockファイルがrootと各プロジェクト配下に存在しているため、それぞれメンテナンスしていく必要があります。 また、rootをbuild contextに指定してdocker buildしているため、プロジェクト配下のlockファイルを更新後にrootのlockファイルを更新し忘れると、docker buildをする際にrootとプロジェクト配下のlockファイルが衝突してビルドが失敗するという事象が発生してしまいます。開発体験としてよろしくない状態だと思っているので改善したいと考えています。

おわりに

エモーションテックでは顧客体験、従業員体験の改善をサポートし、世の中の体験を変えるプロダクトを開発しています。この記事や他の記事を見て少しでも弊社に興味をもっていただけましたら、ぜひ採用ページからご応募をお願いいたします。

https://careers.emotion-tech.co.jp/

参考

https://github.com/adriangb/python-monorepo/tree/main/poetry

https://note.com/tatsuyashirakawa/n/nb3a6fb881e94