CircleCI + LamveryによるLambda functionのdeployベストプラクティス

  • 43
    いいね
  • 2
    コメント
この記事は最終更新日から1年以上が経過しています。

前置き

AWS LambdaはEC2インスタンス無しでイベント駆動で必要な時に必要なだけコードを実行でき、課金も立ち上げているだけで料金がかかるEC2とは違い、完全なる使った分だけ課金という非常に強力で魅力的なサービスです。

しかし、このサービスはその軽量でシンプルなコンセプトとは裏腹に従来無かった独自の概念が多いためか、面倒なことや悩ましいことが多いと感じています。

だからこそ、もっと簡単かつ実用的に使いたいという想いからLamveryというツールを作っている訳なのですが、その中でも実用面で大きな課題となると感じていたデプロイの部分において、現時点で自分の中でのベストプラクティスと言って差し支えないフローを組むことができたので紹介したいと思います。

Lambda functionのdeployにおける3つの課題

①デプロイパッケージの作成

②バージョン管理

③デプロイフローの整備

ベストプラクティスはまだ無いというか、ほとんどこの辺の話を見かけたことがない。1

今回の方法によって解決するもの

①Pythonデプロイパッケージの作成(Node.jsもいけるはず)

まず、Lamvery自体がvirtaulenv内で簡単にデプロイパッケージを作り、それをデプロイするための機能を持っています。なので、CircleCIのPython環境でクリーンなvirutalenv環境を作り、その中で必要なライブラリをインストールしてデプロイするだけです。
単純にカレントディレクトリ以下をzipにまとめてデプロイすることもできるので、Node.jsもpackage.jsonをリポジトリに含めてnpm installを叩かせれば同じようにいけるはずです。2

②Lambdaの特殊なバージョニング仕様を活かしたGit branchとの紐付け

Lambdaのバージョンに付けられるエイリアスを活用し、エイリアスを個々に指定してfunctionの実行が可能である特性を活かして、branchと紐付けた個別の実行環境を提供します。

③Pull Requestベースのデプロイフロー

いわゆるこういうやつ → http://d.hatena.ne.jp/naoya/20140502/1399027655
②ができれば、そりゃできるよねって話ではあるんですが。

方法と手順

ちなみに今回の内容を実際に検証したものがこちらになります。
https://github.com/marcy-terui/lamvery-circleci-deploy

CircleCIで該当のリポジトリを有効にする所は通常と同じなので割愛します。

1. IAM設定

Lambda functionに割り当てるIAM Roleはもちろんですが、CircleCIに設定するIAM Userが必要になります。

Lambda functionに割り当てるIAM Roleのポリシー

これは、そのfunctionに何をさせたいかによりけりなのですが、最低限の権限とLamveryの固有機能であるKMSを利用した機密情報の受け渡しを行う場合は以下の様な権限となります。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": [
                "arn:aws:kms:<region>:<account-number>:key/<key-id>"
            ]
        }
    ]
}

<region>,<account-number>,<key-id>は適宜置き換えてください。
KMSのキーの作り方(key-idの発行)についてはこちら↓
https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/create-keys.html

このRoleは以降の章で使用するので、ARN3を控えておきましょう。

CircleCIに設定するIAM Userのポリシー

ここはあまり変動はないと思います。
一々設定するのが面倒ならlambda:*は全リソース許可("Resource": "*")でもまあ場合によってはアリかなとは思いますが、iam:PassRoleは全リソース許可すると大変危険なので止めたほうが良いです。4

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:*",
                "iam:PassRole"
            ],
            "Resource": [
                "arn:aws:lambda:<region>:<account-number>:function:<function-name>",
                "arn:aws:lambda:<region>:<account-number>:function:<function-name>:*",
                "<function-role-arn>"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "lambda:CreateFunction",
                "lambda:ListFunctions",
                "lambda:ListVersionsByFunction"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

<region>,<account-number>,<function-name>は適宜置き換えてください。
<function-role-arn>には先ほど作成したRoleのARN3が入ります。

このUserのCredentialを次の章で使用しますので、控えておきましょう。

IAM UserのCredentialを取得し、CircleCIへ設定

  1. Project Settingsを開く
    image
  2. AWS Permissionを選択
    image
  3. Credentialを入力する
    image

2. Lamveryのインストールと設定

Lamveryをインストールして設定ファイルを生成

$ pip install lamvery
$ lamvery init
lamvery: Output initial file: .lamvery.yml
lamvery: Output initial file: .lamvery.exclude.yml
lamvery: Output initial file: .lamvery.event.yml
lamvery: Output initial file: .lamvery.secret.yml

設定ファイルを編集する

region及びconfiguration以下の各設定は適宜置き換えてください。
{{ env['AWS_LAMBDA_ROLE'] }}の部分はPrivateリポジトリならRoleのARNくらいはリポジトリに入ってても問題ないかなという気はします。
同じように環境変数から渡したい場合はCircleCI上にて「Project Settings」→「Environment Variables」で設定できます。

.lamvery.yml
profile: null
region: us-east-1
versioning: false
default_alias: master
configuration:
  name: lamvery-deploy-sample
  runtime: python2.7
  role: {{ env['AWS_LAMBDA_ROLE'] }}
  handler: lambda_function.lambda_handler
  description: This is a sample lambda function.
  timeout: 10
  memory_size: 128

設定のポイント

  • versioning: false
    デフォルトではLambdaのバージョニングを無効にして、必要に応じてコマンドオプションで有効化します。
  • default_alias: master
    コマンドオプションで指定しない場合にデプロイ時に付けるエイリアスです。masterじゃなくても良いのですが、オペミス防止の為production用のエイリアス(後述)は避けましょう。

Functionを実装し、必要なライブラリを列挙する

functionの実装はただのPythonコーディングなので割愛します。上記の例ではhandlerlambda_function.lambda_handlerのとなっているので、以下の様なファイルになります。

lambda_function.py
import lamvery


def lambda_handler(event, context):
    print(lamvery.secret.get('foo'))

Pythonプロジェクトではrequirements.txtに書くことが多いので、今回はそのようにしています。virtualenv内で以下のように必要なライブラリをインストールした上でファイルに書き出すという方法がおすすめです。

pip install flake8
pip freeze > requirements.txt

こうすることでCircleCI上で以下を実行することで同じライブラリがインストールできます。

pip install -r requirements.txt

CircleCIの設定を記述

以下のように記述します。

circle.yml
---
machine:
  python:
    version: 2.7

dependencies:
  pre:
    - |
      virtualenv .venv
      source .venv/bin/activate
      pip install -r requirements.txt

test:
  override:
    - |
      source .venv/bin/activate
      flake8 lambda_function.py

deployment:
  master-head:
    branch: master
    commands:
      - |
        source .venv/bin/activate
        lamvery deploy
  staging:
    branch: staging
    commands:
      - |
        source .venv/bin/activate
        lamvery deploy -a staging -p
  production:
    branch: production
    commands:
      - |
        source .venv/bin/activate
        lamvery deploy -a production -p

設定のポイント

  • dependencies.preでクリーンなvirtaulenv環境を作り、その中で必要なライブラリをインストール
    Lamveryもこの中に入っています(機密情報の受け渡しを行わない場合はFunction自体にLamveryは必要ないので、別途インストールしても良いと思います)
  • masterブランチではデプロイ時のコマンドオプション無し
    オプション無しの場合、versioning: false, default_alias: masterとなっているため、常に$LATESTというバージョニングが無効な場合に使われる特殊なバージョン5masterというエイリアスが付くことになります。
    これにより、Lambdaには容量制限があるためmasterブランチの更新で常に新しいバージョンが発行されるのは避けたいが、HEADは常に実行可能という状態にできます。
  • staging及びproductionでは明示的にエイリアス名とバージョニングを有効にするオプションを付ける
    -aでエイリアス名指定、-pでバージョニングを有効にできます。

Let's deploy!

プルリクエストを作ってマージしてみます。

image

デプロイ結果を見てみましょう。

image

色付きのログを見ると、4という新しいバージョンが発行されてそこにproductionのエイリアスが付け替えられているのがなんとなくお分かりいただけるかと思います。

ちなみに併せて production-preというエイリアスが設定されているかと思いますが、これはロールバック用で、以下のように緊急時のロールバックができます。手元で叩いても良いですし、ChatBotなんかに叩かせても良いと思います。

$ lamvery rollback -a production
lamvery: [Function] Previous version: 2
lamvery: [Alias] production: 4 -> 2

デプロイされたfunctionの実行について

各種Event等から実行する場合、エイリアス毎に個別のARN3がついているので注意しましょう。
以下の様な指定になります。APIの場合、Qualifierというパラメータでエイリアスを指定する場合もあります。
arn:aws:lambda:<region>:<account-number>:function:<function-name>:<alias>

まとめ

Lamveryというツールを作る目的の一つであった、自分の中で「これだ!」と思えるLambda functionのデプロイフローができたのでご紹介させてもらいました。
今までEC2等で行われていた手法と大きな違いがなく、違和感なく使えるフローを意識していますが、もっとこうした方が良い等ご意見ありましたら、是非以下からフィードバックいただけると嬉しいです。

https://github.com/marcy-terui/lamvery

TODO: CircleCIの手前までを自動化するCloudFormationテンプレートの作成


  1. 唯一見つけたのがこれ。凄く良く出来てるけど、今回の内容はもっとデプロイに特化していて方向性が違う。 

  2. Node.jsは未検証なのでやってみて結果を報告してもらえると喜びます(Javaは・・・?) 

  3. Amazon Resource Nameの略 

  4. 任意のRoleを割り当てられるようになる→AdminやPowerUser権限のRoleがあれば指定できちゃう→Lambda経由でやりたい放題 

  5. わかりにくい表現だけど他に思いつかない。。。