Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
0
Help us understand the problem. What are the problem?

“Beyond the Twelve-Factor App” & “The Twelve-Factor App”から学びAWSで実践する (3)依存関係管理 / 依存関係 - 依存関係を明示的に宣言し分離する

解説

本ファクターはオリジナルにもBeyondにもある依存関係の管理に関するファクターです。今どきの開発言語であればnpm、pip、gradleといったライブラリの参照等を解決するためのパッケージ管理ツールはほぼ供えられています。アプリケーションの依存関係をそういったパッケージ管理を利用し解決することで、どんな環境でも適切に構成されたアプリケーションの一式が構築でき動作させることが保証されます。また、依存という意味では、アプリケーションのライブラリだけでなく、ビルドスクリプトやデプロイスクリプトなどにも注意が必要です。これらスクリプトが特定のシステムにインストールされているライブラリやコマンドなどに暗黙的に利用してしまわないようにする必要があります。

さらに言うと、私のようにWindows環境でアプリを作っていたりすると、スクリプトがシェルで書かれていたら、それはシステム依存だよなと感じてしまいます。どんなOS上でもランタイムのバージョンさえ揃えられれば。イコールコンディションでアプリケーションの開発・テスト・実行がすべて賄えるという状態になるようことを目標に努力をするべきだと思います。。

実践

今回も前回に引き続きLambdaとPythonをベースにこのファクターを実践してみようと思います。Pythonのpipenvを利用したパッケージ管理と、それをもとにLambda関数を配備するためのパッケージづくりまでの流れで進めていこうと思います。利用するPythonは3.8とし、ランタイムのインストールは完了しているところからスタートします。

pipenv

pipenvとは、pipによるパッケージ管理と、venvによる仮想環境管理を一体化させたツールになります。依存関係を明示的に分離管理するためのツールとして高いレベルにあると思います。

1.pipenvをインストールします。

pip install pipenv

2.pipenvで作成される仮想環境がプロジェクト直下のフォルダにできるように設定を追加します。環境変数に以下を追加してください。

PIPENV_VENV_IN_PROJECT=1

これにより、virtualenvvで作成される仮想環境がプロジェクト固有に作成されるようになります。複数のプロジェクトを同時に扱うといったことも考えると、依存関係の分離観点ではここまで分けることをお勧めします。

3.プロジェクトの初期化をします。

pipenv --python 3.8

プロジェクトフォルダ直下にPipfileファイルと.venvフォルダが作成され準備完了です。

開発用パッケージ

pipenvを利用することで開発環境でのみ利用し、製品コードでは不要のパッケージを管理することが可能です。autopep8pylintといったツールがそれにあたりますので、下記のようにインストールしましょう。

pipenv install --dev autopep8 pylint

boto3

Python版のAWS SDKである boto3はLambdaの実行環境に初期からインストールされています。つまり、実行環境に含まれるboto3を利用する場合であれば開発用パッケージとしてboto3をインストールしておき、製品コードには含まれないようにしておきます。Lambdaのコールドスタート時に、boto3の巨大なコードをロードする時間が短縮されるので効果があります。ただし、実行環境に含まれるboto3には、ユーザー側でバージョンをコントロールできないという弱点があります。AWSにより、Lambdaの実行環境が変更された場合、ある日突然プロダクション環境のboto3のバージョンが変わってしまうというリスクは常に付きまといます。さらに、そのバージョンアップがブレーキングチェンジを含んでいたりした場合、突然動かなくなるということが発生します。また、実行環境に含まれるboto3は多少古いバージョンのモジュールになるので、最新のバージョンのboto3が必要になるようなユースケースでも使えません。
24/365で稼働するようなシステムを運用する場合には、コールドスタートのパフォーマンスは犠牲にしてでも、boto3はアプリケーションに同梱するのがおすすめです。

今回はアプリケーションでboto3を抱え込む形でプロジェクトを構築することとします。以下でパッケージを導入します。

pipenv install boto3

Lambda関数のパッケージング

Lambda関数を配備するためには、依存したパッケージも含め必要なモジュールをZIPに圧縮してアップロードする必要があります。ZIP化するためのプロセスはおおむね以下のような感じです。

1.圧縮用フォルダ作成
2.圧縮用フォルダにpipenvで管理されているパッケージファイルを追加
3.圧縮用フォルダにアプリを追加
4.ZIP化

まずは前提となるプロジェクト構造は以下とします。

root
 + src
 |   + lambda_function.py
 + packing.py
 + Pipfile
 + Pipfile.lock
 + setup.py

srcフォルダ : Lambda関数のアプリケーションです。Lambdaのハンドラーを入れています。
packing.py : Lambda関数のパッケージングを行うコード
Pipfile : pipenvの設定ファイル
Pipfile.lock : pipenvで構築した環境の詳細な情報。完全に環境を再現するためにはこちらのファイルを用いる
setup.py : pipでLambda関数のアプリケーションを配備するための設定ファイル

まずは、setup.pyは以下になります。これはpipコマンドでsrcフォルダ下のアプリ一式をインストールするための設定ファイルですが、これを使ってアプリケーションのパッケージングをします。以下では、src配下のlambda_function.pyとパッケージがあればそれらをインストールします。pipのインストール用の設定になります。

from setuptools import setup, find_packages

setup(
    name="12factor",
    version="1.0.0",
    packages=find_packages(where='src'),
    py_modules=['lambda_function'],
    package_dir={'': 'src'},
)

次に実際にパッケージングするためのpacking.pyは以下になります。pythonで書いているのは、システム依存しないでどのような環境でもZIPを作れるようにするためです。
テンポラリフォルダとして.distに必要なファイルを集め、それをZIPするというのが大まかな流れです。なお、本プログラムは開発環境で実行するのではなく、gitなどからソースコードを取得したクリーンな環境で実行することを想定しています。.venvの仮想環境を一時的に作成することでパッケージングに活用しています。開発環境で.venvの仮想環境を作成しているとそれを破壊するので要注意です。

  1. ZIP用のフォルダを作成します。ゴミが残ってはいけないのでまずは削除⇒フォルダ作成の順で実行します。
  2. pipenv syncコマンドを使ってpipfile.lockで定義されているパッケージ群をZIP用フォルダに集めます。pipenv syncコマンドは忠実にパッケージ群を再現してくれるので、「依存関係の管理」という視点では最も適切なソリューションです。PIP_TARGET環境変数にZIP用のフォルダを指定しておくと。pipenv syncコマンド実行時に依存パッケージだけをそのフォルダにインストールしてくれます。
  3. pip instaa -t .dist .__コマンドでアプリケーションのインストールをZIP用のフォルダに行います。
  4. 最後にZIPファイルを作成します。このlambda_function.zipファイルを使うとLambdaへのデプロイができます。
import os
import shutil
import subprocess

DISTINATION_FOLDER = '.dist'

if __name__ == '__main__':
    # ZIP用のフォルダ作成
    shutil.rmtree(DISTINATION_FOLDER, ignore_errors=True)
    os.mkdir(DISTINATION_FOLDER)
    # pipenv sync時にパッケージを保存するフォルダを変更
    os.environ['PIP_TARGET'] = DISTINATION_FOLDER
    # 仮想環境がすでにあれば削除
    subprocess.call('pipenv --rm')
    # pipfile.lockから仮想環境再現
    subprocess.call('pipenv sync')
    # pipでアプリコードをインストール
    subprocess.call('pip install -t {} .'.format(DISTINATION_FOLDER))
    # ZIP
    shutil.make_archive('lambda_function', 'zip', DISTINATION_FOLDER)
    # 仮想環境削除
    subprocess.call('pipenv --rm')

まとめ

第三のファクターとして、依存関係管理方法をPythonとLambdaを使って実践しました。どこまでセンシティブに依存関係を管理するかはプロジェクトによって変わると思いますが、今回紹介した例は最も厳密に管理した場合に例として参考にしてもらえればと思います。

次回は「(4)設計、ビルド、リリース、実行 / ビルド、リリース、実行 - ビルド、リリース、実行の3つのステージを厳密に分離する」です。
本シリーズの目次
全シリーズの目次

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
0
Help us understand the problem. What are the problem?