はじめに
エモーションテックで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