127
136

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

社内コードを公開せずに内部で共有する方法

Last updated at Posted at 2022-09-12

Read this article in English.

はじめに

見つけやすく、インストールしやすいソフトウェアパッケージは、開発者にとって使いやすいです。ReactRuby on RailsAirflow のような有名な OSS は良い事例です。しかし、社内の非公開のコードは、企業秘密として世間から隠されることが多いです。権限を持っている人のみ見ることができて、オープンソースのように npm gempip で簡単にインストールすることもできません。

その結果、社内のコードがうまく再利用されなくなる(あるいはできなくなる)ことがあります。各チームはそれぞれ独立したコードベースを持ち、他のチームにコードを共有したくても、満足がいく解決策を導き出すことが難しかったりします。戦略を立てないままでは、それぞれの独立したコードベースを充実させ続け「社内共通のライブラリー」が遠い夢のようになっていきます。

あなたの会社でも心当たりがあるのではないでしょうか?では、社内コードを世界に公開せずに、内部で共有するためにはどうしたらいいのでしょうか?

この記事では、2つのやり方をおすすめしたいと思います。

  1. 共通のコマンドラインツールをコンテナ化し、プライベートレジストリに載せる。
  2. 共通のライブラリー(パッケージ)をプライベートリポジトリに載せる。

社内コードを公開せずに内部で共有する方法

1. コマンドラインツールをコンテナ化する

もし、特定のメンバー(例: 社内の開発者)に共有したいコマンドラインツールがあったら、そのツールをコンテナ化し、プライベートコンテナレジストリに載せることをおすすめします。

なぜコンテナ化するのか?

Python、Bash、Rubyなどで作った "ちょっとしたスクリプト" は、その場限りで必要とされる場合には、効率がよく素晴らしいものです。しかし、こういったアドホックなスクリプトを何回も使い続けているのであれば、社内の他の人(将来入社する人、退職した後に業務を引き継ぐ人を含む)にもそのニーズがあるでしょう。だったら、みんなの仕事を少しでも楽にできるように、そのスクリプトを可能な限り再利用した方がいいのではないでしょうか?

例えば、Name タグを指定して AWS EC2 インスタンスを起動するような、短くて簡単なスクリプトを作ったとします。

./start_ec2_instances dev-1 dev-2

しかし、このままでは、以下のような欠点があります。

  • 書いた本人しか使えない
  • Aさんの新しいMacBook Proで動かない
  • SREチームのAさんが同じことをするスクリプトを3ヶ月前に書いたので、2時間を無駄にしてしまった :sweat_smile:

代わりに、社内にコンテナ化されたスクリプトの一覧を用意しましょう。こうすることで、社内の誰もが、開発言語を問わず貢献できて、他の開発者が作ったスクリプトも使えます。

イメージ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でも挙動が同じです。:open_mouth:

buildkitのようなツールで、コンテナイメージを複数のCPUアーキテクチャー向けにビルドできて、docker run 実行時に適切な方を選んでくれます。まるで「どのマシンでも動くバイナリー」のようです。

どのレジストリを使えばいいのか?

会社がソースコードの管理に一つのGitHubまたはGitLabオーガニゼーションを使う場合は、GitHub Container RegistryGitLab 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にまとめると、実行しやすくなります。シェルスクリプトで作業している場合、getoptsMakefile で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 のようなクラウドソリューションで プライベートリポジトリ を作ることをおすすめします。パッケージを載せると、アクセス権のある人は gemnpmpip のような一般的なパッケージマネージャでインストールできます。

アーティファクトレジストリ/リポジトリとは、バイナリファイルやコードパッケージを保存して配布するための仕組みです。例えば、PyPI は、PythonのOSSパッケージ用のレジストリです。npm は、JavaScriptパッケージのレジストリです。

これからは、AWS CodeArtifactでプライベートリポジトリを作成し、Pythonパッケージを載せ、他のアプリケーションからインストールする流れをお見せします。他の言語でも似たような流れになるはずです。

AWS CodeArtifact にプライベートPythonパッケージを載せる

1. CodeArtifactのコンソールを開き、ドメインを作成します。

ドメインは、パッケージをダウンロードする時に使うベースURLのことで、組織/会社ごとに1つあれば十分です。通常は、このURLを指定せずにパッケージをインストールしますが、裏でパブリックリポジトリ(例: pypi.org)のURLが使われています。
domains-jp.png
2. パッケージを保存するためのリポジトリを作成します。前のステップで作ったドメインと関連付けます。
repositories-jp.png
3. 新しいディレクトリで、簡単なPythonパッケージを作成します。この記事では、パッケージ名を test123 とします。

test123
├── test123.py
└── pyproject.toml
test123.py
def say_hello():
    print("hello world")
pyproject.toml
[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_setup.bash
# 選択肢(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_setup.bash
# 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. 最後に、パッケージをビルドして公開します。

twine_upload.bash
python3 -m pip install --upgrade build twine
python3 -m build
python3 -m twine upload --repository test123 dist/*
poetry_upload.bash
poetry build
poetry publish -r test123

これで、CodeArtifactにパッケージが公開されました。リポジトリの詳細画面を開くと、パッケージが表示されます。URLを同僚に共有しましょう。(必要に応じて、コンテナイメージのようにIAMポリシーでパッケージをインストールできるユーザーを制限できます。)
repository-ja.png

プライベートPythonパッケージをインストールする

プライベートPythonパッケージが手に入ったので、Webアプリケーション、Jupyter Notebook、コンテナイメージ、Lambda関数などにインストールできます。

プライベートパッケージをインストールするには、パッケージマネジャーにパッケージのURLを教える必要があります。Pythonの場合は、pip パッケージマネジャーの index-url の設定が必要です。方法は、以下の2つがあります。

1. (推奨) index-url を直接指定する

pip では、--index-url パラメータにプライベートリポジトリの URL を指定できます。poetry では、pyproject.tomlsource プロパティの設定が必要です。いずれの場合、インストールコマンド実行時にパッケージが存在しなかったら、インストールが失敗します。

2. index-url の"デフォルト値"としてプライベートリポジトリのURLを設定する

この場合、パッケージマネジャーは先に公開リポジトリで検索し、パッケージが見つからなかった場合、指定された第2の index-url で検索します。pip では、--extra-index-url パラメータの指定で設定できます。poetry では、pyproject.tomlsecondary フラグを設定します。

悪意を持った人がプライベートパッケージと同じ名前のパブリックパッケージを公開することができます。そのため、pip でパッケージをインストールする際には --extra-index-url 引数を、Poetry では secondary 属性を使用しないことをおすすめします。リポジトリの検索順番を狙った攻撃は dependency confusion attack と呼ばれます。

pip を使う場合

以下のコード例では、AWS CodeArtifact に公開したプライベートパッケージを pip でインストールします。

pip_install.bash
# 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 を指定する方法を紹介します。

requirements.txt
# PyPI(パブリックリポジトリ)から取得するパッケージを記載するファイル
requests
requirements-private.txt
# プライベートリポジトリから取得するパッケージを記載するファイル
--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を指定します。

pyproject.toml
[[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 を設定します。

pyproject.toml
[tool.poetry.dependencies]
test123 = {version = "^0.0.1", source = "test123_codeartifact"}
requests = "*"

Basic HTTP認証でCodeArtifactと認証します。その後、poetry install を実行して、すべてのパッケージを同時にインストールします。

poetry_install.bash
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 を作成するイメージです。

requirements.txt
requests
requirements-private.txt
--index-url https://aws:${CODEARTIFACT_AUTH_TOKEN}@<my_domain>-<account_id>.d.codeartifact.<region>.amazonaws.com/pypi/test123/simple/
test123
pip_example.Dockerfile
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) を使う場合

pyproject.toml
[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"
poetry_example.Dockerfile
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エンジニアを募集しています。
ぜひカジュアル面談でお気軽に話しましょう。

127
136
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
127
136

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?