cimg/pythonでvenvによる仮想環境の作成がエラーになる

# 起きたこと
CircleCIで`cimg/python`イメージを使い、venvで仮想環境を作成してライブラリをインストールしようとしました。

例えば次のようなjobです。
```yaml
jobs:
  test:
    docker:
      - image: cimg/python:3.9
    steps:
      - run:
          command: |
            python3 -m venv venv
            . venv/bin/activate
            pip install --upgrade pip
            python setup.py install
            python -m pytest tests
```

このjobを実行したところ、次のエラーが出ました。
```
Error: Command '['/home/circleci/app/venv/bin/python3', '-Im', 'ensurepip', '--upgrade', '--default-pip']' returned non-zero exit status 1.
```

エラーメッセージでググったところ、sudoをつけてvenvを実行すればいいという投稿を見つけたので試してみたところ、変わらずエラーは出るものの、メッセージが詳細になりました。どうやら仮想環境の作成に失敗しているようです。
```
The virtual environment was not created successfully because ensurepip is not available.  On Debian/Ubuntu systems, you need to install the python3-venv package using the following command.

    apt install python3.10-venv

You may need to use sudo with that command.  After installing the python3-venv
package, recreate your virtual environment.

Failing command: ['/home/circleci/app/venv/bin/python3', '-Im', 'ensurepip', '--upgrade', '--default-pip']
```

# 原因
仮想環境内にpipがインストールできず仮想環境の作成に失敗したことが直接のエラーの原因です。

venvで仮想環境を作成するとき、仮想環境内にpipがインストールされます。この時、グローバル環境に既にpipがインストールされていればそれを仮想環境内にもインストールしますが、グローバルにない場合はensurepipというpipインストーラのブートストラップモジュールが呼ばれてpipが仮想環境にインストールされます。
(補足: pipはPython本体とは独立したリリースサイクルを持つためPython標準ライブラリに含まれておらず、代わりにpipをインストールするためのensurepipが標準ライブラリに含まれています。そのensurepipがPythonのインストール時に自動で実行されるため、通常はpipを明示的にインストールする必要はありません。)

ところが、`cimg/python`のベースになっているUbuntuではensurepipが無効化されているため(グローバル環境にpipが入っているとPython環境を壊してしまう可能性があるためだそうです)、pipがインストールできず上記のエラーが出るというわけです。

# 解決策
以上のことから、仮想環境作成前にグローバル環境にpipをインストールしておくか、もしくは仮想環境内に直接pipをインストールしてしまえば解決します。

### 案1: グローバル環境にpipをインストールする
グローバル環境にpipをインストールしておけば、後は普通に仮想環境を作成すれば、グローバル環境のpipが仮想環境にもインストールされます。
```
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python3 get-pip.py
python3 -m venv venv
. venv/bin/activate
pip install --upgrade pip
```

pipはUbuntuのパッケージマネージャ経由でインストールしてもOKです。
```
sudo apt update
sudo apt install python3-pip
python3 -m venv venv
. venv/bin/activate
pip install --upgrade pip
```

### 案2: pip付属のvenvをインストールする
エラーメッセージでググると時々見かける方法です。

apt経由でインストールできるvenvにはpipが付属しているので(多分)、venvのインストール時にpipもグローバル環境にインストールされます。
後は案1と同じで、venvで仮想環境を作成すればグローバル環境のpipが仮想環境にもインストールされます。

```
sudo apt update
sudo apt install python3-venv
python3 -m venv venv
. venv/bin/activate
pip install --upgrade pip
```

aptでは一部のPythonバージョン向けのvenvしかインストールできないので、アプリケーションの実行環境と揃えたい場合は好ましくない方法かもしれません。
(記事執筆時点では3.10以上向けのvenvしか存在せず、`python3.9-venv`はありませんでした。ちなみに`python3-venv`は存在するのですが、何故か`Package 'python3-venv' has no installation candidate`と言われてインストールに失敗します。)

また、venv自体はPythonに同梱されているので、事実上pipのためだけにvenvをインストールし直すことになり、その点でも微妙な方法です。

### 案3: pipなしで仮想環境を作成し、仮想環境内に直接pipをインストールする
venvの実行時に`--without-pip`オプションを付けるとpipのインストールをスキップして仮想環境を作成することができます。
それを利用して、まずpipなしで仮想環境を作成し、その後仮想環境に入ってpipをインストールします。
```
python3 -m venv --without-pip venv
. venv/bin/activate
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python3 get-pip.py
pip install --upgrade pip
```

### どの方法が良いか
CircleCIを使う場合は独立したコンテナ内で実行されるため正直どの方法でも良いですが、個人的には案1のグローバル環境にpipを入れる方法が本来の`cimg/python`の動作に近くて良いと思います。

# 残る疑問
上記の対応でエラーは解消できたのですが、奇妙なことにこのエラーは同じjob内容でも発生したりしなかったりします。
そもそも`cimg/python`はCircleCI上での動作に最適化されたPythonイメージで、pipは事前にインストールされているので、グローバルにpipが入っていないのもensurepipが無効化されているのもおかしな話です。

全くの想像ですが、CircleCIのバグで`cimg/python`の起動時にensurepipが無効化されてしまうことがあるのかもしれません。

解決案については全て動作することを確認しているものの、解説については全体的に不確かな内容が多いので、間違っている箇所があればご指摘いただけると幸いです。

# 参考にした記事
https://stackoverflow.com/a/70344891
https://va2577.github.io/post/79/
https://amano41.hatenablog.jp/entry/wsl-python3-venv-error
https://stackoverflow.com/questions/62314556/how-to-install-virtualenv-on-ubuntu-20-04-gcp-instance
https://circleci.com/developer/images/image/cimg/python