LambdaLayer用zipをCodeBuildでお手軽に作ってみる。

# 背景

- Lambdaはサーバーレス構成に必須なサービス
- LambdaLayerは複数のLambdaで共通するモジュールを使いまわしできるサービス
- LambdaLayer用のzipを作る際には、その作業用のAmazonLinuxのEC2立てたり、Dockerコンテナ作ったりするのが理想
- 公開されているライブラリだけでなく、自身の共通モジュールも含めたりすると構築環境への転送も必要

一言でいうと、使いたいが面倒くさい。

# CodeBuildがあるじゃないか

[AWS CodeBuild](https://aws.amazon.com/jp/codebuild/) は、CodeCommitやGiuhubなどのソース管理システムからソースをcloneして、各種ビルド処理後に成果物（アーティファクト）をS3にアップロード出来るサービス。
ビルド用のマシンの環境はまっさらな状態（awscliとかの基本的なのは既に入っている）。本ビルド処理前にライブラリのインストール処理とかも可能だったり、独自のAMIを使ったりする事も出来る。

- 他のライブラリなどが入っていない綺麗な環境
- 自分で作ったライブラリなどの参照が可能
- 使った処理時間だけの課金（一日中ビルドとかしてなければ微々たる料金）

という要件に完全マッチしているサービス。

# zip作ってみる

## 準備するもの

LambdaLayerを作ろうとする人は既にあると思います。すいませんがそちらに関しての詳細説明は省かせてもらいます。
また、各種操作で、AWSコンソールをある程度扱った事がある前提の説明になっています。ご了承ください。

### CodeCommitやGithubのリポジトリ

実ビルド処理はbuildspec.ymlというファイル（名前変更可能）をリポジトリから読み込むために必要。
今回はGithubで行っています。CodeCommitの場合、後述のCloudFormationのSourceブロックが以下のようになると思います。

<details><summary>CodeCommitの場合の置換</summary><div>

```yml:
      Source:
        Location: 
          !Join
            - ''
            - - 'https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/'
              - !Ref CodeCommitProject
        Type: CODECOMMIT
```
</div></details>

### S3バケット

結果ファイルを配置する為に必要

### ビルド用のロール

codebuildに適用可能（信頼関係に「codebuild.amazonaws.com」がある）で、S3やcodebuildを実行できるロール。そのArnが必要です。

## CodeBuildプロジェクトをCloudFormationで作成

<details><summary>CloudFormationTemplate</summary><div>

```yml:lambdalayer-codebuild-template.yml
Description:
  codebuild project by cloudformation.

Parameters:
  CodeBuildLambdaLayerProjectName:
    Description: Please enter name of the codebuild project for lambda layer.
    Type: String
    Default: "codebuild-lambdalayer"
  GithubProject:
    Description: Please enter the github project name.
    Type: String
    Default: "my-group/lambda-layer"
  SourceBranch:
    Type: String
    Default: "develop"
  S3BucketNameForAtrifacts:
    Type: String
    Default: "my-lambda-layer-artifacts"
  ArtifactPath:
    Type: String
    Default: "lambdalayer-packaging"
  ArtifactName:
    Type: String
    Default: "lambdalayer-thirdparty.zip"
  BuildServiceRoleArn:
    Type: String
  BuildSpecFilePath:
    Type: String
    Default: "buildspec.yml"

Resources:
  LambdaLayerCodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Ref CodeBuildLambdaLayerProjectName
      Description: codebuild project of build zip file for LambdaLayer.
      ServiceRole: !Ref BuildServiceRoleArn
      Artifacts:
        ArtifactIdentifier: LambdaLayerArtifact
        Type: S3
        Location: !Ref S3BucketNameForAtrifacts
        NamespaceType: NONE
        Path: !Ref ArtifactPath
        Name: !Ref ArtifactName
        Packaging: ZIP
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/standard:4.0-20.09.14
      Source:
        Location:
          !Join
            - ''
            - - 'https://github.com/'
              - !Ref GithubProject
        Type: GITHUB
        BuildSpec: !Ref BuildSpecFilePath
      SourceVersion: !Ref SourceBranch
      TimeoutInMinutes: 10
```
</div></details>

これをローカルに保存しておく。
AWSコンソールから、CloudFormationへ飛んで、スタックの作成=>新しいリソースを使用（標準）=>テンプレートの準備完了=>テンプレートファイルのアップロード=>ファイルの選択で、以下の内容をコピペして保存したファイルを読込。=>パラメーター入力（
BuildServiceRoleArn、GithubProject、S3BucketNameForAtrifactsあたりは環境に応じて変更してください）=>次へ=>次=>スタックの作成


## 実処理ファイル（buildspec.yml）を作成

buildspec.ymlの中で[色々な処理が出来る](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-spec-ref.html)のですが、今回はinstallするだけです。
pythonフォルダを作成し、ライブラリのインストール時に -t オプションで、インストール先フォルダを指定しています。
こちらを前述CodeBuildプロジェクトで指定したリポジトリ、ブランチのルートに配置してpushしておきます。
フォルダの位置やファイル名も上記 BuildSpecFilePathパラメーターで指定可能ですが今回はデフォルトのままです。
今回はnumpyライブラリをインストールしています。

<details><summary>buildspec.yml</summary><div>

```yml:buildspec.yml
version: 0.2

phases:
  install:
    commands:
      - mkdir -p workdir/python
      - pip3 install -t workdir/python numpy
artifacts:
  base-directory: 'workdir'
  files:
    - '**/*'
```
</div></details>

## CodeBuild実行

AWSコンソールでCodeBuildのページに行き、左ペインで ビルド=>ビルドプロジェクト で出てきた一覧で作成されているCodeBuildプロジェクトを選択=>ビルドを開始=>パラメーター入力（基本CodeBuildプロジェクトでの指定値ですが、ブランチ名など変更可能）=>ビルド開始

## S3ファイルのオブジェクトURL確認もしくはダウンロード

AWSコンソール上で、S3を参照すると指定したバケットの指定位置に対象ファイルが出力されています。詳細情報の「オブジェクト URL」を保存しておきます。（もしくは指定していしたS3バケットにファイルが出力されています。AWSコンソールからダウンロードしておきます）

# LambdaLayerを作成

[AWS Lambda Layersでライブラリを共通化](https://qiita.com/t_okkan/items/394a15577bd1aad46ec3) が解りやすいです。
AWSコンソールから、Lambdaページに行き、左ペインで Additional resource 配下にある 「レイヤー」 => レイヤーの作成

- 名前：mytestlayer など
- Amazon S3 からファイルをアップロードするを選択し、S3のアドレスに前述「オブジェクトURL」を指定（ダウンロードした場合には、ローカルに保存していたファイルをアップロード）
- 互換性のあるランタイムにPython3.8を指定


# LambdaLayerを使ったLambda関数を作ってみる

## まずは関数雛形作成

AWSコンソールから、Lambdaページに行き、左ペインで関数を選択。関数の作成

- １から作成
- 名前は適当
- 実行ランタイムにPython3.8を使用

を指定して、関数の作成。

## 使用するLambdaLayerを追加

デザイナーの画面に移動するので、デザイン画面の「Layers(0)」をクリック。レイヤー設定エリアが表示されるので、レイヤーの追加を選択

- カスタムレイヤーを選択
- 先ほど作成したlayerが出てくるのでそれを選択
- バージョンは１

レイヤーの追加を実行。

※今回カスタムで追加したnumpyは、AWSレイヤーを選択すると、その中に用意されています。

## コードの中で使ってみる

以下コードをコードエディタで入力。デプロイボタンを押す

```python:sample
import json
import logging
import numpy as np

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

def lambda_handler(event, context):
    a = np.arange(15).reshape(3, 5)
    logger.debug(a)

    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
```
### テスト

Lambda詳細画面で「テスト」を押す。新しいテストイベントの作成が出てくるので適当な名前を付けて保存。
元の画面に戻って、作成したテストを選んで「テスト」実行。

```console
[DEBUG]	2020-12-03T02:46:46.368Z	2a7c94ee-052c-4f49-bc26-17929de6b66d	[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]]
```

import成功していますね。

# 感想

CodeBuildプロジェクトまでCloudFormation管理する必要はないかもしれませんが、しておくと便利かと思います。
その割にLambda側をCloudFormation管理してませんが、こちらはテスト用なので・・・・
今回はやってませんが、gitリポジトリを参照できるので、独自ソースのLayer化も楽です。
CodeBuild、今回の件や本来のビルド以外にも色々使えそうです。

# 参考にさせて頂いたサイト

[AWS Lambda Layersでライブラリを共通化](https://qiita.com/t_okkan/items/394a15577bd1aad46ec3)