様々な条件下におけるPython開発環境の検討と構築

# はじめに

先日、組織は同じなのですが、異なるプロジェクトが同時並行で開発を進めていくというシチュエーションがありました。それぞれのプロジェクトで開発するものは違うのですが、以下のキーワードは共通であったため、同じ組織においては開発環境をなるべく統一したいという方針で共通開発環境の検討と構築を行いました。

- 言語: Python
- パッケージ管理: poetry
- IDE: Visual Studio Code(以降、VSC)
- プラットフォーム: AWS
- 開発するサービス: Lambda(コンテナイメージ使用)
- リポジトリ・CI/CD: GitHub・GitHub Actions

検討していく中で、プロジェクトに参加する開発者のバックボーンも十分に考慮して取り決めしていったので、検討の過程と実際に構築した環境を頭の整理も兼ねてまとめてみます。

# 前提・背景

検討する上で、手元の環境や開発者のスキルセット等考慮すべき事項は下記の通りです。

- 一部開発者の端末は、VDIで接続した先にあるWindowsOSであり、WSL2を使ってLinuxをインストールすることができない。(利用しているVDI側の仕様)
- 開発者の中には、Pythonやコンテナ操作に慣れていないメンバーがいる。
- 今後同組織の他のプロジェクトでも同様に、開発経験の少ないメンバーが新規参画することがある。

# Python開発環境の検討

当初は各開発者端末(FAT端末)でプロジェクト毎のコンテナイメージを起動しコンテナ内で開発を進めるでよいかな、と考えていたのですが、手元でLinuxが使えない点や今後、開発者が同組織内で増えていくことも考慮して、最終的に2案をまとめました。

案1. 開発EC2(Linux)上にPython開発環境を構築
案2. 開発EC2(Linux)上でコンテナのPython開発環境を起動

それぞれの概要について整理していきます。

## 案1. 開発EC2(Linux)上にPython開発環境を構築

1つ目は開発EC2(Linux)を用意し、各開発者個人のOSユーザを発行、そのホームディレクトリ配下にプロジェクト毎の開発環境を構築していく方法です。

![案1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152958/e2270aa9-444d-8bea-571f-3045a514d6d8.png)

このEC2に各開発者がSSH接続(AWS Systems Manager Session Manager利用)して作業します。接続には、VSCの拡張機能[Remote Development](https://code.visualstudio.com/docs/remote/remote-overview)を使ってVSCのウィンドウから接続します。
各プロジェクトのリポジトリをホームディレクトリにクローンし、その配下でpyenv+poetryの仮想環境を構築し開発していきます。可搬性については目をつぶる一方で、非常にシンプルで学習コストも抑えることができ浸透しやすい構成となっています。
また、動作確認時は開発EC2上でDockerfileからコンテナをビルド、起動することで開発環境から実際のLambda Functionを想定した動作確認が行えるようにしています。

## 案2. 開発EC2(Linux)上でコンテナのPython開発環境を起動

2つ目は開発EC2(Linux)を用意し、各開発者個人のOSユーザを発行、さらに本番でも使用するDockerfileからコンテナを起動して接続、開発を行うといった方法です。

![案2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152958/423bb6d0-9a9c-f7bd-666e-f4a4b0c2335e.png)

EC2を用意して各開発者がSSH接続していく点に変わりはありません。ただ接続後、ホームディレクトリ配下に仮想環境を構築するのではなく、実際に本番へデプロイするコンテナに接続してその中で開発を行います。SSH及びコンテナへの接続は、先程と同様にVSCのRemote Developmentを使ってVSCのウィンドウから接続します。
コンテナであるため、開発EC2にDockerをインストールしておけば初期セットアップはほとんどなく開発環境ができあがってしまうという点、また今回はLambdaでコンテナイメージ使用を想定していたため、実際にLambdaへデプロイされるイメージを使って開発も動作確認も行える点がメリットです。ただ本番のイメージをそのまま開発環境として利用するということで、本番では不要な開発でしか使わないパッケージをインストールし、イメージの肥大化が発生する点はデメリットと考えています。

## 検討の結果

結論から言うと、`1. 開発EC2(Linux)上にPython開発環境を構築`を採用しました。
それぞれメリット・デメリットがあり、また日頃からコンテナを触っている開発者が多ければ案2が選ばれる可能性が高いかなと思いますが、以下の理由から案1を採用しました。

- コンテナの扱いに慣れていないメンバーがいるため、スクリプト化・手順化していない操作に対応できない(余計に時間がかかってしまう)可能性がある。
- Pythonでの開発に慣れていないメンバーがいるため、パッケージ管理含めコンテナでラップするのではなく、まず自身で開発環境を構築することで土台の理解を深めたい。
- pyenv+poetryでの環境構築自体、事例の多い構成であるため、学習コストを抑えながら環境構築できる。

# 開発環境の構築

検討した結果、案1を採用することとなりましたので、最後に案1の環境構築についてまとめておきます。
今回はリポジトリフォルダ配下に仮想環境を構築することを想定しています。
尚、以降の手順は開発EC2にVSCのRemote DevelopmentでSSH接続したところから始める前提です。

## pyenvセットアップ

1. 開発EC2上で以下コマンドを実行し、pyenvをダウンロードします。

   ```bash
   git clone https://github.com/pyenv/pyenv.git ~/.pyenv
   ```

2. pyenvコマンドを実行できるよう、PATHの追加等初期設定を行います。以下コマンドを全文コピーし、開発EC2上のターミナルで実行します。

   ```bash
   cat << EOF >> .bashrc
   
   
   # Load pyenv automatically by appending
   # the following to 
   # ~/.bash_profile if it exists, otherwise ~/.profile (for login shells)
   # and ~/.bashrc (for interactive shells) :
   
   export PYENV_ROOT="\$HOME/.pyenv"
   [[ -d $PYENV_ROOT/bin ]] && export PATH="\$PYENV_ROOT/bin:\$PATH"
   eval "\$(pyenv init -)"
   
   EOF
   ```

3. .bashrcを再読込みし、pyenvコマンドが実行できることを確認します。
   
   ```bash
   . .bashrc
   pyenv --version
   ```

## pyenvにPythonをインストール

1. 現在インストールされているPythonバージョン一覧を確認します。

   ```bash
   pyenv versions
   
   # 以下のように表示されます。
   * system (set by /home/<ユーザー名>/.pyenv/version)
   ```

2. 利用するPythonバージョンをインストールします。(今回は仮に3.11.7と想定)

   ```bash
   pyenv install 3.11.7
   ```

3. 指定したPythonバージョンがインストールされたことを確認します。

   ```bash
   pyenv versions

   # 以下のように表示されます。
   * system (set by /home/<ユーザー名>/.pyenv/version)
   3.11.7  
   ```

4. 自身のユーザーで利用するglobalバージョンを指定します。

   ```bash
   python --version             # 設定前(システムインストールされたバージョンが表示されます。)
   pyenv global 3.11.7
   python --version             # 設定後(3.11.7が表示されます。)
   ```

5. pyenvでもバージョンが切り替わっていることを確認します。`.python-version`というファイルが生成され、このディレクトリ内(配下のサブディレクトリも含む)だけ指定したバージョンで実行されます。

   ```bash
   pyenv versions
   
   # 以下のように表示されます。
   system
   * 3.11.7 (set by /home/<ユーザー名>/.pyenv/version)
   ```

## poetryインストールと仮想環境の初期設定

1. 自身のホームディレクトリにリポジトリをクローンします。(今回は仮にリポジトリ名:testrepoとします。)

   ```bash
   git clone <リポジトリURL>
   ```

2. testrepoに移動し、poetryをインストール、設定します。

   ```bash
   cd testrepo
   
   # poetryインストール
   curl -sSL https://install.python-poetry.org | python3 -

   # インストール確認
   poetry --version

   # .venvをリポジトリの配下に置く設定
   poetry config virtualenvs.in-project true
   ```

3. まだ`pyproject.toml`、`poetry.lock`が生成されていない前提とし、poetryの初期化を行います。

   ```bash
   poetry init

   # 対話式で今回開発するパッケージの概要、Pythonバージョン、インストールするパッケージの情報を入力していきます。
   This command will guide you through creating your pyproject.toml config.
   
   Package name [testrepo]:  
   Version [0.1.0]:
   Description []:
   Author [None, n to skip]:
   License []:
   Compatible Python versions [^3.9]: 3.11.7 # バージョンを3.11.7に固定

   Would you like to define your main dependencies interactively? (yes/no) [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): pandas　 # 例として、追加パッケージにpandasを指定
   [ 0] pandas
   [ 1] Pandas3
   [ 2] pandas2
   [ 3] pandas007
   [ 4] pandas-lite
   [ 5] pandas-illustrated
   [ 6] pandas-coder
   [ 7] gym-pandas
   [ 8] pandas-multiprocess
   [ 9] pandas-plink
   [ 10]
   > 0
   Enter the version constraint to require (or leave blank to use the latest version): 2.1.4  # バージョンを2.14に固定
   
   Add a package (leave blank to skip):

   Would you like to define your development dependencies interactively? (yes/no) [yes]
   Generated file
   
   [tool.poetry]
   name = "testrepo"
   version = "0.1.0"
   description = ""
   authors = ["Your Name <you@example.com>"]
   readme = "README.md"
   
   [tool.poetry.dependencies]
   python = "3.11.7"
   pandas = "2.1.4"

   
   [build-system]
   requires = ["poetry-core"]
   build-backend = "poetry.core.masonry.api"
   
   
   Do you confirm generation? (yes/no) [yes]  
   # pyproject.tomlが生成される
   ```

4. testrepo配下で使用するPythonバージョンを指定します。

   ```bash
   # .python-versionファイルがtestrepo直下に生成される
   pyenv local 3.11.7

   # .venvフォルダがtestrepo直下に生成される
   poetry env use 3.11.7
   ```

5. pyproject.tomlを元に、仮想環境へパッケージのインストールを行います。

   ```bash
   # .venv/lib 配下に指定したパッケージが依存関係が考慮された状態でインストールされる
   poetry install
   # poetry.lockが生成される
   ```

6. VSCのコマンドパレットより、`> Python: インタープリターを選択` → `Python 3.11.7 ('.venv: Poetry) ./.venv/bin/python'`を指定すれば、VSCよいデバッグ実行可能となります。

7. 尚、パッケージを追加・削除する場合は以下コマンドを実行することで、poetryが`pyproject.toml`、`poetry.lock`を自動でアップデートしてくれます。

   ```bash
   # パッケージの追加
   poetry add boto3==1.34.2  # バージョンを1.34.2に固定

   # パッケージの削除
   poetry remove boto3
   ```

# おわりに

今回は開発環境構築の手順だけではなく、組織に合った環境はどのようなものがあるか、何を選択するか、といった検討の過程も含めて情報を整理しました。今回選択した環境が全ての開発組織においてベストであるとは考えていません。所属する開発者のスキルや周辺環境によって、今回挙げた2案とはまた別の環境がベストと判断される場合も往々にしてあると思います。全ての状況で正解と言えるものはないと思いますが、こういった考え方がある、うちの組織には合う or 合わない等々、検討の一助になれば幸いです。