はじめに
見つけやすく、インストールしやすいソフトウェアパッケージは、開発者にとって使いやすいです。React、Ruby on Rails、Airflow のような有名な OSS は良い事例です。しかし、社内の非公開のコードは、企業秘密として世間から隠されることが多いです。権限を持っている人のみ見ることができて、オープンソースのように npm
gem
や pip
で簡単にインストールすることもできません。
その結果、社内のコードがうまく再利用されなくなる(あるいはできなくなる)ことがあります。各チームはそれぞれ独立したコードベースを持ち、他のチームにコードを共有したくても、満足がいく解決策を導き出すことが難しかったりします。戦略を立てないままでは、それぞれの独立したコードベースを充実させ続け「社内共通のライブラリー」が遠い夢のようになっていきます。
あなたの会社でも心当たりがあるのではないでしょうか?では、社内コードを世界に公開せずに、内部で共有するためにはどうしたらいいのでしょうか?
この記事では、2つのやり方をおすすめしたいと思います。
- 共通のコマンドラインツールをコンテナ化し、プライベートレジストリに載せる。
- 共通のライブラリー(パッケージ)をプライベートリポジトリに載せる。
社内コードを公開せずに内部で共有する方法
1. コマンドラインツールをコンテナ化する
もし、特定のメンバー(例: 社内の開発者)に共有したいコマンドラインツールがあったら、そのツールをコンテナ化し、プライベートコンテナレジストリに載せることをおすすめします。
なぜコンテナ化するのか?
Python、Bash、Rubyなどで作った "ちょっとしたスクリプト" は、その場限りで必要とされる場合には、効率がよく素晴らしいものです。しかし、こういったアドホックなスクリプトを何回も使い続けているのであれば、社内の他の人(将来入社する人、退職した後に業務を引き継ぐ人を含む)にもそのニーズがあるでしょう。だったら、みんなの仕事を少しでも楽にできるように、そのスクリプトを可能な限り再利用した方がいいのではないでしょうか?
例えば、Name
タグを指定して AWS EC2 インスタンスを起動するような、短くて簡単なスクリプトを作ったとします。
./start_ec2_instances dev-1 dev-2
しかし、このままでは、以下のような欠点があります。
- 書いた本人しか使えない
- Aさんの新しいMacBook Proで動かない
- SREチームのAさんが同じことをするスクリプトを3ヶ月前に書いたので、2時間を無駄にしてしまった
代わりに、社内にコンテナ化されたスクリプトの一覧を用意しましょう。こうすることで、社内の誰もが、開発言語を問わず貢献できて、他の開発者が作ったスクリプトも使えます。
イメージURI (READMEへのリンク) | 用途 | 問い合わせ先 |
---|---|---|
ghcr.io/<company>/start_ec2_instances | EC2 インスタンスを Name タグで起動する |
Aさん (SRE) |
registry.hub.docker.com/<company>/zips3file | S3へのファイルのダウンロード、zip圧縮、再アップロード | Bさん (Data Science) |
... |
実行方法は、オープンソースのコンテナと同じです。
docker run --rm -v "$HOME/.aws:/root/.aws" ghcr.io/<company>/start_ec2_instances dev-1 dev-2
これで、許可さえあれば誰でも実行できて、Linux、Mac M1/Intel、かつWindowsでも挙動が同じです。
buildkitのようなツールで、コンテナイメージを複数のCPUアーキテクチャー向けにビルドできて、docker run
実行時に適切な方を選んでくれます。まるで「どのマシンでも動くバイナリー」のようです。
どのレジストリを使えばいいのか?
会社がソースコードの管理に一つのGitHubまたはGitLabオーガニゼーションを使う場合は、GitHub Container Registry か GitLab Container Registry をおすすめします。追加料金はかかりませんし、コードと同じプラットフォームで管理できます。
チームごとにオーガニゼーションが分かれていたり、グループ内の他の会社と協力する必要がある場合は、やり方を少し工夫する必要があります。
可能であれば、Docker Hub のようなサービスで「共通コンテナレジストリ」を作成して、全員を招待しましょう。それでも無理なら、Amazon ECR のようなクラウドプロバイダーレジストリをおすすめします。IAMポリシーで他のアカウントのユーザーにコンテナイメージのアクセス権を付与することができて、タグやコンテキストキーで誰にどのコンテナイメージを共有するか制限することもできます。
例えば、以下のIAMポリシーをECRリポジトリに付けると、その中のすべてのコンテナイメージに対して、以下の条件でpush/pullアクセスを許可します。
- リポジトリの
shared
タグがtrue
である - アカウント
123456789012
の IAM ユーザーのみアクセスできる -
job-category = developer
のタグを持つ IAM ユーザーのみアクセスできる - 会社の VPN (IP アドレス
123.45.678.90
) に接続されているユーザーのみアクセスできる
{
"Version": "2012-10-17",
"Id": "CrossAccountECRPolicy1",
"Statement": [
{
"Sid": "AllowPushPull",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::123456789012:user/*"
]
},
"Action": [
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:CompleteLayerUpload",
"ecr:GetDownloadUrlForLayer",
"ecr:InitiateLayerUpload",
"ecr:PutImage",
"ecr:UploadLayerPart"
],
"Condition": {
"ecr:ResourceTag/shared": [
"true"
],
"StringEquals": {
"aws:PrincipalTag/job-category": "developer"
},
"IpAddress": {
"aws:SourceIp": "123.45.678.90"
}
}
}
]
}
このIAMポリシーをすべてのECRリポジトリに追加して、共有したいリポジトリだけ shared
タグを true
に設定すれば、コンテナイメージのURLを公開しても、↑の条件を満たしたユーザーのみアクセスできます。
どうやってコンテナ化するのか?
空っぽのディレクトリを作成し、その中にスクリプトとDockerfile
を置いてください。
start_ec2_instances
├── Dockerfile
├── README.md
├── test123.py
└── requirements.txt
複数の Python スクリプトがある場合、click のようなライブラリーで1つのCLIにまとめると、実行しやすくなります。シェルスクリプトで作業している場合、getopts
や Makefile
で1つの ENTRYPOINT を用意することもおすすめします。
リモートリポジトリと認証して、イメージをビルドしてプッシュします。BUILDKITなどで複数のCPUアーキテクチャーに対応するのもいいでしょう。
export DOCKER_BUILDKIT=1
echo $GCR_PAT | docker login ghcr.io -u USERNAME --password-stdin
docker build -t ghcr.io/<github_account_name>/start_ec2_instance --platform linux/amd64,linux/arm64 .
docker push ghcr.io/<github_account_name>/start_ec2_instance
これで、コンテナイメージをパブリックリポジトリにアップロードできました。共有したいメンバーに使い方を教えてあげてください。
docker run --rm ghcr.io/<github_account_name>/start_ec2_instance dev-1 dev-2
2. 共通のライブラリーをプライベートリポジトリに
もし、特定のメンバーに共有したいソフトウェアパッケージがある場合、AWS CodeArtifact, Google Cloud Artifact Registry または GitHub Packages のようなクラウドソリューションで プライベートリポジトリ を作ることをおすすめします。パッケージを載せると、アクセス権のある人は gem
、npm
や pip
のような一般的なパッケージマネージャでインストールできます。
これからは、AWS CodeArtifactでプライベートリポジトリを作成し、Pythonパッケージを載せ、他のアプリケーションからインストールする流れをお見せします。他の言語でも似たような流れになるはずです。
AWS CodeArtifact にプライベートPythonパッケージを載せる
1. CodeArtifactのコンソールを開き、ドメインを作成します。
ドメインは、パッケージをダウンロードする時に使うベースURLのことで、組織/会社ごとに1つあれば十分です。通常は、このURLを指定せずにパッケージをインストールしますが、裏でパブリックリポジトリ(例: pypi.org)のURLが使われています。
2. パッケージを保存するためのリポジトリを作成します。前のステップで作ったドメインと関連付けます。
3. 新しいディレクトリで、簡単なPythonパッケージを作成します。この記事では、パッケージ名を test123
とします。
test123
├── test123.py
└── pyproject.toml
def say_hello():
print("hello world")
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "test123"
version = "0.0.1"
requires-python = ">=3.7"
4. AWS CodeArtifactで認証を行います。
パッケージをアップロードする前に、AWSと認証を行う必要があります。認証方法は、ビルドツールによって異なります。
- Twineは、Pythonパッケージを公開するのによく使われるユーティリティで、AWS CodeArtifactでtwineを使用する方法を説明する公式ドキュメントがあります。
- Poetryも、Pythonパッケージを作成/公開するためによく使われるツールです。執筆時点では、Poetryに関するAWSドキュメントは存在しませんが、こちらのブログ記事が参考になります。
以下のコード例で、↑両方のツールで認証を行う方法を紹介します。
# 選択肢(1) ログインコマンドを使う
aws codeartifact login --tool twine --domain <my_domain> --repository test123
# 選択肢(2) 環境変数を使う
export TWINE_USERNAME=aws
export TWINE_PASSWORD=`aws codeartifact get-authorization-token --domain <my_domain> --query authorizationToken --output text`
export TWINE_REPOSITORY_URL=`aws codeartifact get-repository-endpoint --domain <my_domain> --repository test123 --format pypi --query repositoryEndpoint --output text`
# Poetryはログインコマンドがなく、環境変数を使う必要がある
export CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token --domain <my_domain> --query authorizationToken --output text`
# 形式: https://<my_domain>-<account_id>.d.codeartifact.<region>.amazonaws.com/pypi/test123/
export CODEARTIFACT_REPOSITORY_URL=`aws codeartifact get-repository-endpoint --domain <my_domain> --repository test123 --format pypi --query repositoryEndpoint --output text`
export CODEARTIFACT_USER=aws
poetry config repositories.test123 $CODEARTIFACT_REPOSITORY_URL
poetry config http-basic.test123 $CODEARTIFACT_USER $CODEARTIFACT_AUTH_TOKEN
macOSでpoetryがBasic HTTP認証に失敗した場合、このGitHub Issueが参考になります。
5. 最後に、パッケージをビルドして公開します。
python3 -m pip install --upgrade build twine
python3 -m build
python3 -m twine upload --repository test123 dist/*
poetry build
poetry publish -r test123
これで、CodeArtifactにパッケージが公開されました。リポジトリの詳細画面を開くと、パッケージが表示されます。URLを同僚に共有しましょう。(必要に応じて、コンテナイメージのようにIAMポリシーでパッケージをインストールできるユーザーを制限できます。)
プライベートPythonパッケージをインストールする
プライベートPythonパッケージが手に入ったので、Webアプリケーション、Jupyter Notebook、コンテナイメージ、Lambda関数などにインストールできます。
プライベートパッケージをインストールするには、パッケージマネジャーにパッケージのURLを教える必要があります。Pythonの場合は、pip パッケージマネジャーの index-url の設定が必要です。方法は、以下の2つがあります。
1. (推奨) index-url を直接指定する
pip
では、--index-url
パラメータにプライベートリポジトリの URL を指定できます。poetry
では、pyproject.toml
の source
プロパティの設定が必要です。いずれの場合、インストールコマンド実行時にパッケージが存在しなかったら、インストールが失敗します。
2. index-url の"デフォルト値"としてプライベートリポジトリのURLを設定する
この場合、パッケージマネジャーは先に公開リポジトリで検索し、パッケージが見つからなかった場合、指定された第2の index-url で検索します。pip
では、--extra-index-url
パラメータの指定で設定できます。poetry
では、pyproject.toml
の secondary
フラグを設定します。
悪意を持った人がプライベートパッケージと同じ名前のパブリックパッケージを公開することができます。そのため、pip
でパッケージをインストールする際には --extra-index-url
引数を、Poetry では secondary
属性を使用しないことをおすすめします。リポジトリの検索順番を狙った攻撃は dependency confusion attack と呼ばれます。
pip を使う場合
以下のコード例では、AWS CodeArtifact に公開したプライベートパッケージを pip
でインストールします。
# 1. 認証トークンを取得する
export CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token --domain <my_domain> --query authorizationToken --output text`
# 2. インストール時に --index-url パラメータを指定します。
# HTTP Basic認証のパスワードには、(1)のトークンを使用します。
python3 -m pip install --index-url "https://aws:${CODEARTIFACT_AUTH_TOKEN}@<my_domain>-<account_id>.d.codeartifact.<region>.amazonaws.com/pypi/test123/simple/" test123==0.0.1
なぜ URL に /simple/
を付け足しているのか?
Pythonパッケージリポジトリには、様々な実装があります。公式な実装(PyPIが使用するもの)は simple と呼ばれます。simple API をもとに作られたリポジトリのベースURLは、仕様上 /simple/
で終わる必要があります。詳しくは PEP 503 をご確認ください。
しかし、多くの場合、Pythonアプリケーションの依存パッケージを requirements.txt
ファイルに記載します。次の例では、requirements.txt
ファイルに --index-url
を指定する方法を紹介します。
# PyPI(パブリックリポジトリ)から取得するパッケージを記載するファイル
requests
# プライベートリポジトリから取得するパッケージを記載するファイル
--index-url https://aws:${CODEARTIFACT_AUTH_TOKEN}@<my_domain>-<account_id>.d.codeartifact.<region>.amazonaws.com/pypi/test123/simple/
test123
pipでは、requirements.txt
内でPOSIX形式の環境変数(例: ${API_TOKEN}
)が利用できます。
poetry (<1.2.0) を使う場合
執筆時点で最新 poetry バージョンが 1.2 ですが、動作確認でプライベートパッケージのインストールが成功せず 1.1.15 をもとに事例紹介させていただいています。1.2 以上での動作確認が出来次第、この記事に追記します。
poetryでパッケージを管理しているアプリケーションに、プライベートパッケージをインストールするには、まず pyproject.toml
ファイルに tool.poetry.source
を追加し、リポジトリ名とURLを指定します。
[[tool.poetry.source]]
name = "test123_codeartifact" # name は CodeArtifact のリポジトリ名と違っても大丈夫
url = "https://<my_domain>-<account_id>.d.codeartifact.<region>.amazonaws.com/pypi/test123/simple/"
次に、インストールするプライベートパッケージの source
プロパティに tool.poetry.source
ブロックの name
を設定します。
[tool.poetry.dependencies]
test123 = {version = "^0.0.1", source = "test123_codeartifact"}
requests = "*"
Basic HTTP認証でCodeArtifactと認証します。その後、poetry install
を実行して、すべてのパッケージを同時にインストールします。
export CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token --domain <my_domain> --query authorizationToken --output text`
export CODEARTIFACT_USER=aws
poetry config http-basic.test123 $CODEARTIFACT_USER $CODEARTIFACT_AUTH_TOKEN
poetry install
プライベートパッケージ名/バージョンがPyPI上のパブリックパッケージと重複した場合、インストールコマンドは失敗することがあります。この問題は、ローカルのpoetryキャッシュを削除することで解決できます。
完全例: コンテナイメージビルド時にプライベートパッケージをインストールする
では、CIなどでコンテナイメージをビルドする時に、どのようにプライベートパッケージをインストールできるのでしょうか?
AWS CodeArtifact から、get-authorization-token
API経由で認証情報を取得します。その際、--duration-seconds
で認証情報の有効期限を指定できます。このパラメータを15分などの低い値に設定すると、プライベートパッケージをインストールするための認証情報をそのまま --build-arg
でコンテナイメージに渡すことができ、レイヤー履歴から隠す必要はありません。
export CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token \
+ --duration-seconds 900 \
--domain <my_domain> \
--query authorizationToken \
--output text`
docker build \
--build-arg CODEARTIFACT_USER=aws \
--build-arg CODEARTIFACT_AUTH_TOKEN=$CODEARTIFACT_AUTH_TOKEN \
--tag example \
.
pipを使う場合
pipを使ったコンテナイメージのビルド事例です。複数のプライベートリポジトリがある場合、リポジトリごとに requirements-*.txt
を作成するイメージです。
requests
--index-url https://aws:${CODEARTIFACT_AUTH_TOKEN}@<my_domain>-<account_id>.d.codeartifact.<region>.amazonaws.com/pypi/test123/simple/
test123
FROM python:3.10.6
ARG CODEARTIFACT_USER
ARG CODEARTIFACT_AUTH_TOKEN
COPY requirements.txt requirements-private.txt .
# requirements-private.txt sets the --index-url to AWS CodeArtifact repository URL
RUN pip install -r requirements.txt \
&& pip install -r requirements-private.txt
poetry (<1.2.0) を使う場合
[tool.poetry]
name = "app"
version = "0.1.0"
description = ""
authors = ["example <example@example.com>"]
[[tool.poetry.source]]
name = "test123_codeartifact"
url = "https://<my_domain>-<account_id>.d.codeartifact.<region>.amazonaws.com/pypi/test123/simple/"
[tool.poetry.dependencies]
python = "^3.10"
test123 = {version = "*", source = "test123_codeartifact"}
requests = "*"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
FROM python:3.10.6
ARG CODEARTIFACT_USER
ARG CODEARTIFACT_AUTH_TOKEN
ARG CODEARTIFACT_REPOSITORY_URL
ENV POETRY_VERSION=1.1.15 \
POETRY_HOME=/usr/local
COPY pyproject.toml poetry.lock .
RUN apt update \
&& apt install -y wget \
&& apt clean \
&& wget https://install.python-poetry.org -O - | python - \
&& poetry config repositories.test123 $CODEARTIFACT_REPOSITORY_URL \
&& poetry config http-basic.test123 ${CODEARTIFACT_USER} ${CODEARTIFACT_AUTH_TOKEN} \
&& poetry install \
&& poetry config --unset http-basic.test
終わりに
みなさんの会社でも「社内のコードを共有するパターン」を作ってはいかがでしょうか?
今回紹介したような方法で、社内の非公開のコードを世間に公開することなく、Lambda関数、Webアプリケーション、Jupyter Notebookなどからインストールできるようになります。開発者同士のコラボレーション/知識共有もしやすくなり、Win-Winですね。
コンテナを起動するには docker run
を、Pythonパッケージをインストールするには pip install
を実行するのは、開発者が慣れている作業なので、社内のコードでも同じようにインストールして使えるようにしましょう!
参考にした記事
私と一緒に働きませんか?
ENECHANGE株式会社では、大量な電力時系列データの分析を中心に、様々なアプリケーション企画/開発を行なっています。例えば、こんなことやっています…
- 電気の市場価格によって、家電を自動的に遠隔操作するアプリケーション開発
- 大量の電気消費量データのクラスター分析や需要予測
- Airflowを活用したデータパイプライン開発
- AWS Elastic Container Serviceで1,000以上のFargateノードを活用した分散処理
等々。
学習意欲が高く、技術的なチャレンジがほしいPythonエンジニアを募集しています。
ぜひカジュアル面談でお気軽に話しましょう。