特定のパッケージだけ AWS CodeArtifact からインストールしたい – pip の場合

2022.03.15

今回は、タイトルのとおり pip でプライベートなパッケージだけ CodeArtifact を使う設定についてです。npmMaven とは異なり pip 固有の問題があったので、その対応方法をまとめてみました。
本記事よりも他にもっといい方法があれば是非ご連絡いただければと思います!

解決したい課題

AWS CodeArtifact を使っているとき、次のように構成したいと思うことがあります。

  • 自作のプライベートなパッケージは、AWS CodeArtifact のリポジトリからインストールしたい
  • その他のパブリックなパッケージは、PyPI からインストールしたい

しかし、AWS CLI を使ってログインすると、下記のような内容で ~/.config/pip/pip.conf が作成されます。

~/.config/pip/pip.conf

[global]
index-url = https://aws:[YOUR_CODEARTIFACT_AUTH_TOKEN]@mydomain-[YOUR_AWS_ACCOUNT_ID].d.codeartifact.ap-northeast-1.amazonaws.com/pypi/myrepo/simple/

この index-urlpip install 時に pip コマンドが検索する デフォルトの URL です。
pip.conf が上記のような内容だとパッケージが CodeArtifact に存在しない場合、インストールすることができません。

しかし、AWS CodeArtifact のリポジトリに対して、アップストリームレジストリとして PyPI を設定すると、CodeArtifact に存在しないパッケージは、CodeArtifact がプロキシとなって PyPI からダウンロードすることができます。
ただ、そのパッケージ群は全て CodeArtifact 上にキャッシュされることになり、課金の対象となります。

この方法でも先程の課題を解決することは可能ですが、キャッシュの課金を回避する方法についても考えてみることにしました。本記事ではこの方法を仮に「プロキシ案」と呼ぶことにします。

01-codeartifact-cache-proxy-diagram

一方で pip 独自の課題もあります。今回の課題を解決するには、npm の「スコープ」のような仕組みを使うと適切なリポジトリをうまく使い分けることができるように思います。
しかし PyPI の場合、同様の概念をサポートしていないので、PyPI と CodeArtifact に登録するパッケージ名が重複しないように登録・管理する必要があります。

下記の記事では、npm のスコープを用いた方法が紹介されています。

AWS ドキュメントの参考箇所は下記です。

本記事では、PyPI においてどのように CodeArtifact を利用するのがいいか考えた内容をまとめてみました。

解決案

公式ドキュメントに記載のものも含めて解決案はいくつかあります。

  • 解決案 1:
    • デフォルトのリポジトリは CodeArtifact として、PyPI からインストールする時は --index-url, -i オプションで明示的に PyPI の URL を指定する。
    • 例:pip install -i https://pypi.org/simple requests

02-kahisaku-1

  • 解決案 2:
    • デフォルトのリポジトリは PyPI として、CodeArtifact からインストールする時は --index-url オプションで明示的に CodeArtifact のURL を指定する。

03-kahisaku-2

  • 解決案 3:
    • pip.conf を編集して、PyPI に存在しないパッケージは CodeArtifact からインストールするようにする

04-kahisaku-3

解決案 1 の検証

冒頭の課題で説明した通り、codeartifact login --tool pip コマンドを実行すると ~/.config/pip/pip.confindex-url = [CodeArtifact の URL] という設定が記載されます。そのため pip install で参照するリポジトリが CodeArtifact だけになってしまいます。またプロキシとして振る舞う場合もパッケージがキャッシュされてしまいます。

そのため pip install を実行する度にリポジトリを PyPI に指定することで問題を回避します。この「解決案 1」については、下記のドキュメントにも記載されています。

しかし利用頻度としては PyPI にあるパッケージの方が多いと思うので、下記のように都度 PyPI のURL を指定するのは使いづらいように感じます。
(指定を忘れてしまったり、環境の詳細を知らない人が使うときに思わぬエラーに遭遇する原因にもなります)

  • PyPI からインストールする場合
$ pip install -U --index-url https://pypi.org/simple [package]
  • CodeArtifact からインストールする場合
# AWS CLI はトークンの有効期間内であれば 2 回目以降を省略可能
$ aws codeartifact login \
    --tool pip \
    --domain [DOMAIN] \
    --domain-owner [YOUR_AWS_ACCOUNT_ID] \
    --repository [REPOSITORY]
 
$ pip install -U [package]

解決案 2 の検証

「解決案 1」 では、デフォルトのリポジトリが CodeArtifact だったので、これを PyPI に指定するように変更します。下記のように pip config を実行することで、明示的にデフォルトリポジトリを PyPI とする設定が ~/.config/pip/pip.conf に書き込まれます。

$ pip config set global.index-url https://pypi.org/simple

次に、CodeArtifact のURL を取得します。CodeArtifact の URL は下記の形式です。

https://aws:[CODEARTIFACT_AUTH_TOKEN]@[DOMAIN]-[AWS_ACCOUNT_ID].d.codeartifact.[REGION].amazonaws.com/pypi/[REOPSITORY]/simple

実際のコマンドは下記のとおりです。認証トークン(CODEARTIFACT_AUTH_TOKEN)は aws codeartifact get-authorization-token コマンドで取得できるので、次のようにして CodeArtifact から対象パッケージをインストールできるようになります。

$ CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token --domain [DOMAIN] --domain-owner [AWS_ACCOUNT_ID] --query authorizationToken --output text`

$ CodeArtifactURL="https://aws:${CODEARTIFACT_AUTH_TOKEN}@[DOMAIN]-[AWS_ACCOUNT_ID].d.codeartifact.[REGION].amazonaws.com/pypi/[REOPSITORY]/simple"

$ pip instal -U --index-url ${CodeArtifactURL} [package]

PyPI にあるパブリックなパッケージについては、従来と変わらないコマンドでインストール可能です。

$ pip install -U [package]

まとめると「解決案 2」では下記のようになります。

  • CodeArtifact にあるプライベートなパッケージは明示的に URL を指定してインストール
    • 認証トークンの入った有効期限のある URL を生成
    • 生成した URL を指定してパッケージをインストール
  • 通常のパブリックなパッケージは従来と変わらない方法でインストール可能
    • pip install -U [package]

「プライベートなパッケージだけ URL を指定してインストールする」というのは自然な感じがします。

解決案 3

解決案 1、解決案 2 では明示的に対象のリポジトリ URL を --index-url オプションで指定するものでした。これが面倒な場合 pip.conf で URL を切り替えることも可能です。(注意点は後述します)

解決案 2 と同様に、デフォルトのリポジトリは PyPI としたいので先程と同じコマンドを実行して pip.conf を構成します。

$ pip config set global.index-url https://pypi.org/simple

次に、CodeArtifact を追加の URL としてセットします。extra-index-urlとしてセットすることで該当パッケージが PyPI に存在しなければ CodeArtifact を参照するようになります。

$ CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token --domain [DOMAIN] --domain-owner [AWS_ACCOUNT_ID] --query authorizationToken --output text`

$ CodeArtifactURL="https://aws:${CODEARTIFACT_AUTH_TOKEN}@[DOMAIN]-[AWS_ACCOUNT_ID].d.codeartifact.[REGION].amazonaws.com/pypi/[REOPSITORY]/simple"

$ pip config set global.extra-index-url ${CodeArtifactURL}

上記のコマンドを実行すると ~/.config/pip/pip.conf は次のようになります。

[global]
index-url = https://pypi.org/simple
extra-index-url = https://aws:[CODEARTIFACT_AUTH_TOKEN]@[DOMAIN]-[AWS_ACCOUNT_ID].d.codeartifact.[REGION].amazonaws.com/pypi/[REOPSITORY]/simple/

これでリポジトリの種類を意識することなくインストールできるようになります。

$ pip install -U [package]

注意点

index-urlextra-index-url は優先度の設定はできないようなので、PyPI と CodeArtifact に同じ名前のパッケージが存在すると意図した動作が期待できません。

上記の issue では解決策として、devpi パッケージを使ってローカルに PyPI のキャッシュサーバを作ることが提案されています。しかし、これはキャシュサーバの位置を除けば「CodeArtifact を PyPI のプロキシとして設定しつつ、デフォルトのリポジトリとする」こと(デフォルト案)と同じ構成です。

パッケージが重複する可能性が完全には拭えないので、解決案 3 はやや不安が残る構成になってしまいます。
下記は devpi に関する参考ページです。

まとめ

各解決案を比較すると「解決案 2」もしくは「CodeArtifact をプロキシとして設定する(デフォルト案)」のいずれかが良さそうに思いました。

料金 運用
デフォルト案 ・全てCodeArtifact 経由でインストールするので運用が楽
・キャッシュサイズに応じた課金が発生
解決案 1 ・PyPI からインストールする時に URL 指定が必要
・CodeArtifactの課金を節約できる
解決案 2 ・CodeArtifact からインストールする時に URL 指定が必要
・CodeArtifactの課金を節約できる
解決案 3 ・リポジトリの URL を都度指定しなくていいので楽
・各リポジトリで同一パッケージが存在する可能性に注意が必要

キャッシュするサイズ次第ですが、CodeArtifact の料金が許容できるならリポジトリの URL は常に CodeArtifact にしておけばいいので「デフォルト案」の方が利用しやすくなります。

一方で、CodeArtifact のキャッシュ料金が気になるようであれば「解決案 2」が使いやすいかと思います。

最後に

AWS CodeArtifact を使ってみて気になった所を確認してみました。ユースケースに応じた適切な設計で使っていけるようにしたいですね。

(他にもっといい方法があればぜひ教えてほしいです!)

以上です。

参考ページ