LoginSignup
15
9

ElixirでAWS LambdaでGithubActionsで継続デプロイ

Last updated at Posted at 2020-01-21

はじめに

「Elixirで簡単にAWS Lambdaの開発ができるようになれば、もっとElixirが流行るのでは?」

そんな想いから、Elixirで実装されたAWS Lambdaの関数をGithubにプッシュすると、Github Actionsが自動でビルドしAWSにデプロイしてくれる仕組みを試してみました。

ElixirでAWS Lambda

AWSの公式としてElixir用のAWS Lambdaランタイムは用意されていないので、ElixirでAWS Lambdaを動かすためには、Elixirで実装したコードを動かすためのカスタムのAWS Lambdaランタイムを用意する必要があります。

また、Elixirでは1.9からmix releaseでElixirやErlang不要で実行可能なパッケージを作成することができるのですが、このパッケージを実行するためには、パッケージを実行する環境で作成する必要があり、AWS Lambdaの場合は、Amazon Linuxの環境でビルドする必要があります。

つまり、ElixirでAWS Lambdaを動かす場合、Amazon Linuxの環境にてElixirの実行可能なパッケージの作成、パッケージとbootstrapを含めたZipファイルの作成、作成されたZipファイルをAWS Lambdaに関数登録、という手順を踏むことになります。

この辺りを実現するライブラリやDockerイメージについては既に作成・公開しているものがあるので、今回はそちらを利用して試してみます。

ライブラリはこちらで公開
→ https://github.com/imahiro-t/faas_base
→ https://hex.pm/packages/faas_base

ハンドラの実装

まずはmix new upcaseとプロジェクトを作成し、mix.exsにAWS Lambda用のライブラリを追加し、アプリケーションとして起動します。

  def application do
    [
      mod: {FaasBase.Aws.Application, []}
    ]
  end

  defp deps do
    [
      {:faas_base, "~> 1.2.0"}
    ]
  end
$ mix deps.get

次にupcase.exに下記のようにハンドラを実装します。(サンプルとして小文字を大文字にするだけの簡単なLambdaです)

defmodule Upcase do
  use FaasBase, service: :aws
  alias FaasBase.Logger
  alias FaasBase.Aws.Request
  alias FaasBase.Aws.Response
  @impl FaasBase
  def init(context) do
    # call back one time
    {:ok, context}
  end
  @impl FaasBase
  def handle(%Request{body: body} = request, event, context) do
    Logger.info(request)
    Logger.info(event)
    Logger.info(context)
    {:ok, Response.to_response(body |> String.upcase, %{}, 200)}
  end
end

LambdaのインプットはJson形式にする必要があり、渡されたJson形式のデータはMap型として引数のeventに展開され、環境変数等はMap型としてcontextに展開されます。

戻り値は{:ok, String.t}か{:error, String.t}のように文字列のみを返すか、{:ok, FaasBase.Aws.Response.t}か{:error, FaasBase.Aws.Response.t}のようにヘッダー情報やステータスコードを含めた情報を返す必要があります。

試しにコンソールでハンドラの動作確認をしてみます。

$ iex -S mix
iex> alias FaasBase.Aws.Request
iex> Upcase.handle(%Request{body: "{\"data\": \"hello\"}"}, %{}, %{})
{:ok,
 %FaasBase.Aws.Response{
   body: "{\"DATA\":\"HELLO\"}",
   headers: %{},
   status_code: 200
 }}

問題なさげです。

Zipファイルの作成

ビルド用のDockerイメージが既にあるので、下記のコマンドで簡単にZipファイルを作成することができます。

$ mkdir -p _build
$ docker run -d -it --rm --name elx erintheblack/elixir-lambda-builder:al2_1.16.2
$ docker cp mix.exs elx:/tmp
$ docker cp lib elx:/tmp
$ docker exec elx /bin/bash -c "mix deps.get; MIX_ENV=prod mix aws.release"
$ docker cp elx:/tmp/_aws ./_build
$ docker stop elx

関数の登録

下記コマンドでAWS Lambdaに関数を登録します。

$ cd ./_build/_aws
$ aws lambda create-function \
    --function-name my-function \
    --runtime provided.al2 \
    --zip-file fileb://upcase-0.1.0.zip \
    --handler MyApp \
    --role arn:aws:iam::XXXXXXXX:role/XXXrole_for_lambdaXXX

動作確認

下記コマンドで動作確認してみます。

$ aws lambda invoke \
    --function-name my-function \
    --payload '{ "data": "hello" }' \
    response.json; cat response.json

{
    "ExecutedVersion": "$LATEST", 
    "StatusCode": 200
}
{"data":"HELLO"}

問題なくLambda関数が実行されました。

Github Actionsで継続デプロイ

Github ActionsではGithubに対する各種イベントをトリガーとしてアクションを設定することができるので、今回はmasterにプッシュされた時にソースをビルドしAWSにデプロイする設定を行ってみます。

AWSへのデプロイはserverlessを利用します。

serverless.ymlの作成

serverlessを実行するための./serverless.ymlは下記のようになります。

service: upcase

provider:
  name: aws
  runtime: provided.al2
  region: ap-northeast-1

package:
  artifact: ./upcase-0.1.0.zip

functions:
  upcase:
    handler: Upcase
    environment:
      LOG_LEVEL: "INFO"
    memorySize: 128
    timeout: 10

カスタムランタイムなのでruntimeprovidedを指定します。環境変数にLOG_LEVELDEBUG, INFO, WARN, ERRORで設定することができ、ライブラリのログ出力のレベルを指定することができます。

ワークフローの作成

ワークフローの.github/workflows/main.ymlは下記のようになります。

name: CI

on:
  push:
    branches:
      - master

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

    - name: Pull Docker Image for Build
      run: |
        docker run -d -it --name elx erintheblack/elixir-lambda-builder:al2_1.16.2

    - name: Build
      run: |
        docker cp mix.exs elx:/tmp
        docker cp lib elx:/tmp
        docker exec elx /bin/bash -c "mix deps.get; MIX_ENV=prod mix aws.release"
        docker cp elx:/tmp/_aws/upcase-0.1.0.zip .
        docker stop elx

    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '18'

    - name: Deploy to Lambda
      uses: serverless/github-action@v3.2
      with:
        args: deploy
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

ビルド用のDockerをプルしてビルドを行い、serverlessのインストールをしてデプロイします。

AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYについてはGithubのシークレットに登録してそちらを参照するようにします。

動作確認

ソースコードをプッシュするとGithub Actionsが動き出し、AWSにデプロイが行われます。

さいごに

ライブラリやGithub Actionsを利用することで、開発者はハンドラの実装にのみ注力することができるようになりました。

ElixirでのLambdaの開発はベストプラクティスとは言い難いかもですが、Elixirでコードを書くこと自体が楽しいので個人的には満足です。

15
9
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
15
9