summaryrefslogtreecommitdiff
path: root/posts/serverless-framework-for-racket.sxml
blob: 9fcd5caa9e5889532dfab82b119002a82ae7e67b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
(use-modules (haunt utils))

`((title . "Serverless Frameworkを使ってRacketのプログラムをAWS Lambdaにデプロイしてみた")
  (id . "serverless-framework-for-racket")
  (date . ,(string->date* "2020-03-26 01:50"))
  (content
   (p "Qiita を退会したときに消えてしまった記事を復元したものです。"
      "ただし完全な復元ではなく、Linux を GNU/Linux に置き換えています。")
   (h2 "目的")
   (p "RacketのプログラムをServerless FrameworkでAWS Lambdaにデプロイして実行したい。")
   (h2 "背景")
   (p "昨年の年末にAWSが"
      (a (@ (href  "https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html")) "Custom AWS Lambda Runtimes")
      "という機能を発表し、任意のプログラミング言語がLambdaで使用できるようになり、マイナーなプログラミング言語のユーザの間で話題になりました。")
   (p "私にとってはプログラミング言語RacketをAWS Lambdaで利用できるようになったことが喜ばしく、業務でしか関わることのなかったAWS Lambdaを趣味で利用しようという気になるほどインパクトの大きい発表でした。")
   (p "しかし、待っていたところでRacketを本気で使用するためのLambda Runtimeが登場することはなさそうだったので新しく作る必要がありました。")
   (h2 "先行事例")
   (h3 "事例1")
   (p "既にRacketのプログラムのLambda Runtimeを作成する方法について紹介されています。" (br)
      (a (@ (href "https://qiita.com/ojima-h/items/9f42f90c2e63a80584d7")) "https://qiita.com/ojima-h/items/9f42f90c2e63a80584d7"))
   (p "しかし、この記事ではあくまでもRacketをSchemeとして使用することが前提となっているために、本気でRacketをAWS Lambdaで使いたい場合には不足していました。")
   (p "具体的には以下の問題が存在します。")
   (ul (li (code "#lang") "によって言語を選択できない")
       (ul (li (code "#lang racket") "のデフォルトのloadによって読み込んでいるため"))
       (li "カスタムランタイムにないpackageを使用することができない")
       (ul (li "たとえば" (a (@ (href "カスタムランタイムにないpackageを使用することができない")) "aws") "のような package を使うことができない")))
   (p "いずれもSchemeのCustom AWS Lambda Runtimeとしては問題のないことですが、"
      (a (@ (href "https://felleisen.org/matthias/manifesto/sec_pl-pl.html"))
         "Programming-Language Programming Language")
      "であるRacketにとっては致命的な問題です。この問題を解決するために、新しくRacketをLambdaへデプロイする方法を確立する必要があります。")
   (h3 "事例2")
   (p "実は、AWSがCustom AWS Lambda Runtimeに対応する前から任意のプログラミング言語が実行可能でした。") (br)
   (a (@ (href "https://www.lambrospetrou.com/articles/aws-lambda-meets-racket/")) "https://www.lambrospetrou.com/articles/aws-lambda-meets-racket/")

   (p "こちらの方法は、別の言語とRacketのプログラムをコンパイルした結果のバイナリを一緒にデプロイし、初期化時にRacketのプログラムを起動してLambda関数が呼ばれる度に、引数を標準入力へ流し込むことによってLambda関数でRacketのプログラムを実行しています。")

   (p "しかし、この方法はCustom AWS Lambda Runtimeが登場する前では大変有益でしたが、現在はこのような遠回りをする必要はありません。また、AWS Lambdaでは標準出力がログ出力に割り当てられていますが、この方法では標準出力が結果の返却に使われてしまうという点で都合がよくありません。")
   (p "よってこの方法は今となっては微妙なのでやはり新しく作る必要がありました。")

   (h2 "Serverless Frameworkについて")
   (p (a (@ (href "https://serverless.com/")) "Serverless Framework")
      "自分でサーバの管理はしたくなくて、冗長性の確保やオートスケーリングなどのインフラ側を誰かに代わりにやって欲しいと思う人にはうってつけのツールです。詳しくはリンク先を参照してください。")
   (p "今回の記事では、AWSのLambda関数とLambda Runtimeのデプロイを行うためにServerless Frmeworkを使用することにしました。")

   (h2 "やったこと")
   (p "Severless Frameworkを使ってRacketのプログラムをAWS Lambdaにデプロイするために行ったことを紹介します。")

   (h3 "aws-bootstrap-runtimeの作成")
   (p (a (@ (href "https://github.com/tojoqk/aws-lambda-bootstrap-runtime"))
         "aws-bootstrap-runtime")
      "とは、Custom AWS Lambda Runtimeの実装をLambda関数側に移すためのCusmtom AWS Lambda Runtimeです。")
   (p "......というと意味不明ですが、このCustom AWS Lambda Runtimeのやることは単純で、ただLambda関数としてデプロイされたbootstrapという名前の実行ファイルを実行するだけです。")

   (p (a (@ (href "https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html")) "こちら")
      "のチュートリアルで説明されている通り、Custom AWS Lambda Runtimeとはbootstrapという名前の実行ファイルを用意して、そこで初期化処理やLambda関数が呼ばれた際の処理などを行うことによって実装されるものです。")

   (p "私も最初は真面目にbootstrapファイルにRacketのLambda Runtimeを一生懸命実装していたのですが、Minimal Racketでは不十分なのでFullのRacketをインストールしようとすると、Racketに同封された実際には不要なライブラリとドキュメントによって容量が膨れ上がりLambda Runtimeに置けるデータの容量の上限を超えてしまったり、実装に失敗するたびにいちいちバージョンを上がって永久に戻せなかったり、RacketのpackageをLambda関数側でデプロイするのが困難だったりしました。
")
   (p "そこで、Custom AWS Lambda Runtimeの実装はRacketのライブラリとして提供し、Racketのプログラムをコンパイルした結果をbootstrapという名前にして、aws-bootstrap-runtimeにデプロイすることによって解決しました。")

   (p "ちなみに、このbootstrap runtimeはCustom AWS Lambda Runtimeが使用できる全てのリージョン対してデプロイされていて、全てのアカウントからアクセス可能な状態になっています。")

   (h3 "aws-lambda-serverless")

   (p "RacketのCustom AWS Lambda Runtimeの構築が面倒くさいという問題は解決したのですが、今度はLambda関数側のデプロイが大変になりました。
Custom AWS Lambda Runtimeの実装を含みつつ、Lambda関数に対応したRacketの手続きが呼び出されるようにしないといけません。
この問題を解決するのが"
      (a (@ (href "https://github.com/tojoqk/aws-lambda-serverless"))
         "aws-lambda-serverless")
      "です。")
   (p "これはServerless Frameworkを使用することを前提としたライブラリで、Lambda関数としてRacketの手続きを簡単にデプロイすることができます。")
   (h2 "実例")
   (p "では試しに、" (code "Hello World") "を返すようなLambda関数をこのライブラリを使って作ってみましょう。
しかし、ここで残念なお知らせがあり、今のところ64bitのGNU/Linux環境からしかデプロイできません(さらにAmazon GNU/Linux 2からしか試していません)。
これ以外の環境で作業している人はVMやコンテナ技術などの仮想環境を使用するか、GNU/LinuxなどのOSがインストールされたマシンを用意しましょう")
   (p "また、"
      (a (@ (href "https://download.racket-lang.org/")) "Racket")
      "と"
      (a (@ (href "https://serverless.com/framework/docs/getting-started/")) "Serverless Framework")
      "がインストールされていることが前提です。
もしもインストールしていない場合は、必ずインストールしましょう。")
   (h3 "aws-lambda-serverlessをインストールする")
   (pre (code "raco pkg install https://github.com/tojoqk/aws-lambda-serverless.git"))
   (h3 "Lambda関数として呼び出すRacketの手続きを実装する")
   (pre (code
         ,(string-join
           (list "#lang racket"
                 "(require json)"
                 ""
                 "(define (hello jsexpr)"
                 "  (jsexpr->string"
                 "   (hash 'body \"Hello, world!\""
                 "         'input jsexpr)))"
                 "(provide hello)")
           "\n")))
   (p "これを、 handler.rkt という名前で保存します。")

   (h3 "serverless.ymlを準備する(ただし、先頭に" (code "#lang aws-lambda-serverless") "と記述する必要があります)")
   (pre (code
         ,(string-join
           (list "#lang aws-lambda-serverless"
                 ""
                 "service: test"
                 ""
                 "provider:"
                 "  name: aws"
                 "  runtime: provided"
                 "  memorySize: 128"
                 "  timeout: 1"
                 "  region: ap-northeast-1"
                 ""
                 "functions:"
                 "  hello:"
                 "    handler: handler.hello"
                 "    layers:"
                 "      - arn:aws:lambda:ap-northeast-1:488514468674:layer:bootstrap:2")
           "\n")))
   (h3 "serverless.ymlをコンパイルしてbootstrapという名前の実行可能なバイナリを作る")
   (pre (code "raco exe --orig-exe -o bootstrap serverless.yml"))
   (h3 "デプロイする")
   (pre (code "sls deploy"))
   (p "これでLambda関数がデプロイできたはずです。
Lambda関数を呼び出して" (code "Hello, world!") "を含むJSONが返ってきたら成功です。")
   (pre (code "sls invoke -f hello"))
   (h2 "まとめ")
   (p "以上によってRacketのプログラムを気軽にAWS Lambdaにデプロイできるようになりました。
RacketでAWS Lambdaを使ってみたいなと思ったら是非参考にしてみてください。")))