[PYTHON] Learn from “Beyond the Twelve-Factor App” & “The Twelve-Factor App” and practice on AWS (3) Dependency Management / Dependencies-Explicitly declare and separate dependencies

Commentary

This factor is a dependency management factor that is both original and beyond. Most modern development languages have package management tools for resolving library references such as npm, pip, and gradle. Resolving application dependencies using such package management ensures that a well-configured set of applications can be built and run in any environment. Also, in terms of dependencies, you need to pay attention not only to application libraries, but also to build scripts and deployment scripts. You need to prevent these scripts from being implicitly used for libraries, commands, etc. installed on a particular system.

Furthermore, when I make an app in a Windows environment like me, if the script is written in a shell, I feel that it is system-dependent. As long as you have a runtime version on any OS. I think we should make efforts with the goal of being able to cover all the development, testing, and execution of applications in equal conditions. ..

Practice

Continuing from last time, I will try to practice this factor based on Lambda and Python. I will proceed with the flow from package management using Python pipenv to creating a package for deploying Lambda functions based on it. Python to be used is 3.8, and the runtime installation will start from the point where it is completed.

pipenv pipenv is a tool that integrates package management with pip and virtual environment management with venv. I think it is at a high level as a tool for explicitly separating and managing dependencies.

  1. Install pipenv.
pip install pipenv
  1. Add settings so that the virtual environment created by pipenv can be placed in the folder directly under the project. Add the following to your environment variables:
PIPENV_VENV_IN_PROJECT=1

This will make the virtual environment created by virtualenvv project-specific. Considering that multiple projects are handled at the same time, it is recommended to separate them so far from the viewpoint of dependency separation.

  1. Initialize the project.
pipenv --python 3.8

The Pipfile file and the .venv folder are created directly under the project folder, and you are ready to go.

Development package

By using pipenv, it is possible to use it only in the development environment and manage unnecessary packages in the product code. Tools such as autopep8 and pylint correspond to it, so install it as follows.

pipenv install --dev autopep8 pylint

boto3

The Python version of the AWS SDK, boto3, is pre-installed in the Lambda runtime environment. In other words, if you want to use boto3 included in the execution environment, install boto3 as a development package so that it is not included in the product code. This is effective because it reduces the time to load the huge code of boto3 at the cold start of Lambda. However, boto3 included in the execution environment has a weakness that the version cannot be controlled by the user. If AWS changes the Lambda execution environment, there is always the risk that the version of boto3 in the production environment will suddenly change one day. Furthermore, if the version upgrade includes a braking change, it may suddenly stop working. Also, boto3 included in the execution environment is a slightly older version of the module, so it cannot be used in use cases where the latest version of boto3 is required. When operating a system that runs 24/365, it is recommended that boto3 be included in the application, even at the expense of cold start performance.

This time, we will build a project by embracing boto3 in the application. Install the package below.

pipenv install boto3

Lambda function packaging

In order to deploy Lambda functions, you need to zip and upload the required modules, including the dependent packages. The process for ZIP conversion is roughly as follows.

  1. Create a folder for compression
  2. Add the package file managed by pipenv to the compression folder
  3. Add the app to the compression folder
  4. ZIP

First of all, the prerequisite project structure is as follows.

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

__src folder __: An application for Lambda functions. Contains a Lambda handler. packing.py: Code for packaging Lambda functions Pipfile: pipenv configuration file Pipfile.lock: Detailed information about the environment built with pipenv. Use this file to completely reproduce the environment setup.py: Configuration file for deploying Lambda function applications with pip

First of all, setup.py is as follows. This is a configuration file for installing a set of apps under the src folder with the pip command, which is used to package the application. In the following, lambda_function.py and packages under src, if any, will be installed. This is the setting for installing 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'},
)

Next, packing.py for actual packaging is as follows. I am writing in python so that I can create ZIP in any environment without depending on the system. The general flow is to collect the necessary files in .dist as a temporary folder and zip them. In addition, this program is not executed in the development environment, but it is assumed to be executed in a clean environment where the source code is obtained from git etc. I am using it for packaging by temporarily creating a virtual environment of .venv. Be careful because if you create a virtual environment of .venv in the development environment, it will be destroyed.

  1. Create a folder for ZIP. Since there should be no garbage left, first delete it and then create a folder.
  2. Use the pipenv sync command to collect the packages defined in pipfile.lock in the ZIP folder. The pipenv sync command faithfully reproduces the packages, making it the most appropriate solution from a "dependency management" perspective. If you specify a folder for ZIP in the PIP_TARGET environment variable. pipenv sync Only dependent packages will be installed in that folder when you run the command.
  3. Use the pip instaa -t .dist __ . command to install the application in the ZIP folder.
  4. Finally, create a ZIP file. You can use this lambda_function.zip file to deploy to Lambda.
import os
import shutil
import subprocess

DISTINATION_FOLDER = '.dist'

if __name__ == '__main__':
    #Create a folder for ZIP
    shutil.rmtree(DISTINATION_FOLDER, ignore_errors=True)
    os.mkdir(DISTINATION_FOLDER)
    #Change the folder where packages are saved during pipenv sync
    os.environ['PIP_TARGET'] = DISTINATION_FOLDER
    #Delete if virtual environment already exists
    subprocess.call('pipenv --rm')
    # pipfile.Virtual environment reproduction from lock
    subprocess.call('pipenv sync')
    #Install app code with pip
    subprocess.call('pip install -t {} .'.format(DISTINATION_FOLDER))
    # ZIP
    shutil.make_archive('lambda_function', 'zip', DISTINATION_FOLDER)
    #Delete virtual environment
    subprocess.call('pipenv --rm')
    

Summary

As a third factor, I practiced dependency management methods using Python and Lambda. I think that how sensitive dependency management is managed depends on the project, but I hope that the example introduced this time can be used as an example when it is managed most strictly.

Next time, "(4) Design, Build, Release, Execution / Build, Release, Execution --- Strictly separate the three stages of Build, Release, and Execution". Table of Contents of this series Table of Contents of All Series

Recommended Posts

Learn from “Beyond the Twelve-Factor App” & “The Twelve-Factor App” and practice on AWS (3) Dependency Management / Dependencies-Explicitly declare and separate dependencies