Fly.ioへGleamアプリをデプロイ
やろうやろうと思っていてなかなな着手できなかったが、
先日Fly.ioのアカウントを作成したので、やっておく。
デプロイ方法については、↓の記事を参考にする。
なお、Ggleamのバージョンはv0.28.3
で試している。
最初の処理。
gleam new pidgey
cd pidgey
gleam add mist gleam_http gleam_erlang
- 「pidgey」と言うGleamのプロジェクトを作成
- プロジェクトのルートディクトリに移動
- 以下の設定を追加
- Webサーバー「mist」を追加
- ヘルパータイプ&関数の「gleam_http」(HTTPランタイム)と「gleam_erlang」(Erlangランタイム)を追加
結果、以下のようなプロジェクト構成となる。
tree .
.
├── README.md
├── build
│ └── packages
│ ├── gleam.lock
│ ├── gleam_erlang
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── gleam.toml
│ │ ├── include
│ │ │ ├── gleam@erlang@process_Abnormal.hrl
│ │ │ ├── gleam@erlang@process_CalleeDown.hrl
│ │ │ ├── gleam@erlang@process_Cancelled.hrl
│ │ │ ├── gleam@erlang@process_ExitMessage.hrl
│ │ │ ├── gleam@erlang@process_ProcessDown.hrl
│ │ │ ├── gleam@erlang@process_ProcessMonitor.hrl
│ │ │ ├── gleam@erlang@process_Subject.hrl
│ │ │ ├── gleam@erlang_ApplicationFailedToStart.hrl
│ │ │ └── gleam@erlang_UnknownApplication.hrl
│ │ └── src
│ │ ├── gleam
│ │ │ ├── erlang
│ │ │ │ ├── atom.gleam
│ │ │ │ ├── charlist.gleam
│ │ │ │ ├── file.gleam
│ │ │ │ ├── os.gleam
│ │ │ │ └── process.gleam
│ │ │ └── erlang.gleam
│ │ ├── gleam@erlang.erl
│ │ ├── gleam@erlang@atom.erl
│ │ ├── gleam@erlang@charlist.erl
│ │ ├── gleam@erlang@file.erl
│ │ ├── gleam@erlang@os.erl
│ │ ├── gleam@erlang@process.erl
│ │ ├── gleam_erlang.app.src
│ │ └── gleam_erlang_ffi.erl
│ ├── gleam_http
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── gleam.toml
│ │ ├── include
│ │ │ ├── gleam@http@cookie_Attributes.hrl
│ │ │ ├── gleam@http@request_Request.hrl
│ │ │ └── gleam@http@response_Response.hrl
│ │ └── src
│ │ ├── gleam
│ │ │ ├── http
│ │ │ │ ├── cookie.gleam
│ │ │ │ ├── request.gleam
│ │ │ │ ├── response.gleam
│ │ │ │ └── service.gleam
│ │ │ └── http.gleam
│ │ ├── gleam@http.erl
│ │ ├── gleam@http@cookie.erl
│ │ ├── gleam@http@request.erl
│ │ ├── gleam@http@response.erl
│ │ ├── gleam@http@service.erl
│ │ ├── gleam_http.app.src
│ │ ├── gleam_http_native.erl
│ │ └── gleam_http_native.mjs
│ ├── gleam_otp
│ │ ├── LICENCE
│ │ ├── README.md
│ │ ├── gleam.toml
│ │ ├── include
│ │ │ ├── gleam@otp@actor_Ready.hrl
│ │ │ ├── gleam@otp@actor_Spec.hrl
│ │ │ ├── gleam@otp@intensity_tracker_IntensityTracker.hrl
│ │ │ ├── gleam@otp@supervisor_ChildSpec.hrl
│ │ │ ├── gleam@otp@supervisor_Spec.hrl
│ │ │ ├── gleam@otp@system_StatusInfo.hrl
│ │ │ ├── gleam@otp@task_Exit.hrl
│ │ │ └── gleam@otp@task_Task.hrl
│ │ └── src
│ │ ├── gleam
│ │ │ └── otp
│ │ │ ├── actor.gleam
│ │ │ ├── intensity_tracker.gleam
│ │ │ ├── node.gleam
│ │ │ ├── port.gleam
│ │ │ ├── supervisor.gleam
│ │ │ ├── system.gleam
│ │ │ └── task.gleam
│ │ ├── gleam@otp@actor.erl
│ │ ├── gleam@otp@intensity_tracker.erl
│ │ ├── gleam@otp@node.erl
│ │ ├── gleam@otp@port.erl
│ │ ├── gleam@otp@supervisor.erl
│ │ ├── gleam@otp@system.erl
│ │ ├── gleam@otp@task.erl
│ │ ├── gleam_otp.app.src
│ │ └── gleam_otp_external.erl
│ ├── gleam_stdlib
│ │ ├── LICENCE
│ │ ├── README.md
│ │ ├── gleam.toml
│ │ ├── include
│ │ │ ├── gleam@dynamic_DecodeError.hrl
│ │ │ ├── gleam@iterator_Iterator.hrl
│ │ │ ├── gleam@iterator_Next.hrl
│ │ │ ├── gleam@queue_Queue.hrl
│ │ │ ├── gleam@regex_CompileError.hrl
│ │ │ ├── gleam@regex_Match.hrl
│ │ │ ├── gleam@regex_Options.hrl
│ │ │ ├── gleam@set_Set.hrl
│ │ │ └── gleam@uri_Uri.hrl
│ │ └── src
│ │ ├── gleam
│ │ │ ├── base.gleam
│ │ │ ├── bit_builder.gleam
│ │ │ ├── bit_string.gleam
│ │ │ ├── bool.gleam
│ │ │ ├── dynamic.gleam
│ │ │ ├── float.gleam
│ │ │ ├── function.gleam
│ │ │ ├── int.gleam
│ │ │ ├── io.gleam
│ │ │ ├── iterator.gleam
│ │ │ ├── list.gleam
│ │ │ ├── map.gleam
│ │ │ ├── option.gleam
│ │ │ ├── order.gleam
│ │ │ ├── pair.gleam
│ │ │ ├── queue.gleam
│ │ │ ├── regex.gleam
│ │ │ ├── result.gleam
│ │ │ ├── set.gleam
│ │ │ ├── string.gleam
│ │ │ ├── string_builder.gleam
│ │ │ └── uri.gleam
│ │ ├── gleam@base.erl
│ │ ├── gleam@bit_builder.erl
│ │ ├── gleam@bit_string.erl
│ │ ├── gleam@bool.erl
│ │ ├── gleam@dynamic.erl
│ │ ├── gleam@float.erl
│ │ ├── gleam@function.erl
│ │ ├── gleam@int.erl
│ │ ├── gleam@io.erl
│ │ ├── gleam@iterator.erl
│ │ ├── gleam@list.erl
│ │ ├── gleam@map.erl
│ │ ├── gleam@option.erl
│ │ ├── gleam@order.erl
│ │ ├── gleam@pair.erl
│ │ ├── gleam@queue.erl
│ │ ├── gleam@regex.erl
│ │ ├── gleam@result.erl
│ │ ├── gleam@set.erl
│ │ ├── gleam@string.erl
│ │ ├── gleam@string_builder.erl
│ │ ├── gleam@uri.erl
│ │ ├── gleam_stdlib.app.src
│ │ ├── gleam_stdlib.erl
│ │ ├── gleam_stdlib.mjs
│ │ └── persistent-hash-map.mjs
│ ├── gleeunit
│ │ ├── LICENCE
│ │ ├── README.md
│ │ ├── gleam.toml
│ │ └── src
│ │ ├── gleeunit
│ │ │ └── should.gleam
│ │ ├── gleeunit.app.src
│ │ ├── gleeunit.erl
│ │ ├── gleeunit.gleam
│ │ ├── gleeunit@should.erl
│ │ ├── gleeunit_ffi.erl
│ │ ├── gleeunit_ffi.mjs
│ │ └── gleeunit_progress.erl
│ ├── glisten
│ │ ├── README.md
│ │ ├── gleam.toml
│ │ ├── include
│ │ │ ├── glisten@acceptor_AcceptorState.hrl
│ │ │ ├── glisten@acceptor_Pool.hrl
│ │ │ ├── glisten@handler_Handler.hrl
│ │ │ ├── glisten@handler_LoopState.hrl
│ │ │ ├── glisten@handler_Ssl.hrl
│ │ │ ├── glisten@handler_Tcp.hrl
│ │ │ ├── glisten@socket@transport_Ssl.hrl
│ │ │ └── glisten@socket@transport_Tcp.hrl
│ │ └── src
│ │ ├── glisten
│ │ │ ├── acceptor.gleam
│ │ │ ├── handler.gleam
│ │ │ ├── logger.gleam
│ │ │ ├── socket
│ │ │ │ ├── options.gleam
│ │ │ │ └── transport.gleam
│ │ │ ├── socket.gleam
│ │ │ ├── ssl.gleam
│ │ │ └── tcp.gleam
│ │ ├── glisten.app.src
│ │ ├── glisten.erl
│ │ ├── glisten.gleam
│ │ ├── glisten@acceptor.erl
│ │ ├── glisten@handler.erl
│ │ ├── glisten@logger.erl
│ │ ├── glisten@socket.erl
│ │ ├── glisten@socket@options.erl
│ │ ├── glisten@socket@transport.erl
│ │ ├── glisten@ssl.erl
│ │ ├── glisten@tcp.erl
│ │ ├── ssl_ffi.erl
│ │ └── tcp_ffi.erl
│ ├── mist
│ │ ├── README.md
│ │ ├── gleam.toml
│ │ ├── include
│ │ │ ├── mist@handler_Response.hrl
│ │ │ ├── mist@handler_State.hrl
│ │ │ ├── mist@http_Buffer.hrl
│ │ │ ├── mist@http_FileBody.hrl
│ │ │ ├── mist@http_Read.hrl
│ │ │ ├── mist@http_Unread.hrl
│ │ │ ├── mist@websocket_BinaryFrame.hrl
│ │ │ ├── mist@websocket_BinaryMessage.hrl
│ │ │ ├── mist@websocket_CloseFrame.hrl
│ │ │ ├── mist@websocket_PingFrame.hrl
│ │ │ ├── mist@websocket_PongFrame.hrl
│ │ │ ├── mist@websocket_TextFrame.hrl
│ │ │ ├── mist@websocket_TextMessage.hrl
│ │ │ └── mist@websocket_WebsocketHandler.hrl
│ │ └── src
│ │ ├── http_ffi.erl
│ │ ├── mist
│ │ │ ├── encoder.gleam
│ │ │ ├── file.gleam
│ │ │ ├── handler.gleam
│ │ │ ├── http.gleam
│ │ │ ├── logger.gleam
│ │ │ └── websocket.gleam
│ │ ├── mist.app.src
│ │ ├── mist.erl
│ │ ├── mist.gleam
│ │ ├── mist@encoder.erl
│ │ ├── mist@file.erl
│ │ ├── mist@handler.erl
│ │ ├── mist@http.erl
│ │ ├── mist@logger.erl
│ │ └── mist@websocket.erl
│ └── packages.toml
├── gleam.toml
├── manifest.toml
├── src
│ └── pidgey.gleam
└── test
└── pidgey_test.gleam
「Hello MzRyuKa!!!」を常に返すWebアプリケーションのコード。
バージョンアップなどの関係で、元のコードから修正が必要になっている。
変更前:
import mist
import gleam/io
import gleam/erlang
import gleam/bit_builder
import gleam/http/response.{Response}
pub fn main() {
// Start the web server
assert Ok(_) = mist.run_service(8080, web_service)
// Put the main thread to sleep while the server works
erlang.sleep_forever()
}
fn web_service(_request) {
let body = bit_builder.from_string("Hello, Joe!")
Response(200, [], body)
}
変更後
import mist
import gleam/io
//import gleam/erlang ... (1)
import gleam/erlang/process
import gleam/bit_builder
import gleam/http/response.{Response}
pub fn main() {
io.println("Hello from pidgey!")
// Start the web server
// assert Ok(_) = mist.run_service(8080, web_service) ... (2)(3)
let assert Ok(_) =
mist.run_service(8080, web_service, max_body_limit: 4_000_000)
// Put the main thread to sleep while the server works
// erlang.sleep_forever() ... (4)
process.sleep_forever()
}
fn web_service(_request) {
let body = bit_builder.from_string("Hello, MzRyuKa!")
Response(200, [], body)
}
修正ポイント
(1) : sleep_foreverを利用するモジュールのimport文変更
後続の(4)と関係。
無限ループを行う関数sleep_forever
であるが、それを利用するモジュールがerlang
からprocess
に変わっている。
そのため、モジュールの宣言も、import gleam/erlang
からimport gleam/erlang/process
に変更する。
(2): assert利用時のシンタックスの変更
以前はassert
単体で定義できていたが、v0.27
からのリリースで let assert
のシンタックスへ変更されている。
そのため、受け側はlet assert
へ修正。
(3): mist.run_serviceの引数の変更
mist.run_service
の引数の個数が3個に増えている。
従来の「port番号(Int)」、「hanndler(handler.Handler)」の他に、「max_body_limit(Int)
:レスポンスのbodyのサイズ上限」が追加されている。なお、max_body_limitの値は、bytes
となる。
ちなみに、ソースコードとしては以下が該当。
pub fn run_service(
port: Int,
handler: handler.Handler,
max_body_limit max_body_limit: Int,
) -> Result(Nil, glisten.StartError) {
handler
|> handler.with(max_body_limit)
|> acceptor.new_pool_with_data(handler.new_state())
|> glisten.serve(port, _)
}
なお、ソースコードについては、ローカルの以下の場所にある。
- build/packages/mist/src/mist.gleam
(4): sleep_foreverを利用するモジュール名の変更
(1)でも触れたが、sleep_forever
を利用できるモジュールがgleam/erlang/process
になっている。
そのため、process.sleep_forever()
に変更している。
ちなみに、ソースコードとしては以下が該当。
- https://github.com/gleam-lang/erlang/blob/main/src/gleam/erlang/process.gleam
- https://github.com/gleam-lang/erlang/blob/main/src/gleam/erlang/process.gleam#L359-L360
pub external fn sleep_forever() -> Nil =
"gleam_erlang_ffi" "sleep_forever"
なお、ソースコードについては、ローカルの以下の場所にある。
- build/packages/gleam_erlang/src/gleam/erlang/process.gleam
おまけ
なお余談であるが、io.println("Hello from pidgey!")
は本来不要であるのだが、import gleam/io
が他で利用されていないためThis imported module is never used.
と警告を出してくるのでわざと残してある。
さて、実行。
$gleam run
Compiling gleam_stdlib
Compiling gleam_erlang
Compiling gleam_otp
Compiling glisten
Compiling gleam_http
Compiling mist
Compiling gleeunit
Compiling pidgey
Compiled in 23.08s
Running pidgey.main
Hello from pidgey!
問題なし。
では、curlコマンドで確認。
curl localhost:8080
レスポンスは以下。成功。
Hello, MzRyuKa!
デプロイ準備。
GleamをFly.ioで動作させるには、Dockerで対応する。
GleamのベースとなるImageファイルを探す。
Gleamのイメージは、ghcr.io/gleam-lang
にある。
Githubの画面から遷移するときはこっちから。
URL的にはこちら。
遷移すると、Imageの候補が出ていくる。
上の方に出てくるのはnightly
なので、View all tagged versions
をクリックして、リリースバージョンのものを選択しておく。
個人的に、Elixirも入れておきたいので、今回はv0.28.3-elixir
のイメージを利用してみる。
イメージのパスとしては、以下。
FROM ghcr.io/gleam-lang/gleam:v0.28.3-elixir
Dockerファイルの作成。
ファイルの置き場所は、pidgeyディレクトリ直下
FROM ghcr.io/gleam-lang/gleam:v0.28.3-elixir
# Add project code
COPY . /build/
# Compile the project
RUN cd /build \
&& gleam export erlang-shipment \
&& mv build/erlang-shipment /app \
&& rm -r /build
# Run the server
WORKDIR /app
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["run"]
デプロイコマンドの実行。
flyctl launch
でデプロイ開始。いくつか確認されるので答えていく。
Update available 0.0.522 -> 0.0.538.
Run "flyctl version update" to upgrade.
Creating app in /<path>/<to>/work/gleam/v0.28/pidgey
Scanning source code
Detected a Dockerfile app
? Choose an app name (leave blank to generate one): pidgey
automatically selected personal organization: MzRyuKa
Some regions require a paid plan (fra, maa).
See https://fly.io/plans to set up a plan.
? Choose a region for deployment: Tokyo, Japan (nrt)
App will use 'nrt' region as primary
Created app 'pidgey' in organization 'personal'
Admin URL: https://fly.io/apps/pidgey
Hostname: pidgey.fly.dev
? Would you like to set up a Postgresql database now? No
? Would you like to set up an Upstash Redis database now? No
? Create .dockerignore from 1 .gitignore files? No
Wrote config file fly.toml
? Would you like to deploy now? Yes
今回の場合の、質問愛用と回答はこちら。
- ? Choose an app name (leave blank to generate one): pidgey
- 「アプリの名前何にする?」→「pidgey」
- ? Choose a region for deployment: Tokyo, Japan (nrt)
- 「デプロイ先のリージョンどこにする?」→「Tokyo(ナリタ?)」
- ? Would you like to set up a Postgresql database now? No
- 「Postgresqlのセットアップする?」→「 No」
- ? Would you like to set up an Upstash Redis database now? No
- 「Redislのセットアップする?」→ 「No」
- ? Create .dockerignore from 1 .gitignore files? No
- 「.dockerignore作る?」→ 「No」
- ? Would you like to deploy now? Yes
- 「今すぐデプロイする?」→ 「Yes」
でデプロイ開始。
デプロイ中
デプロイが終わったら、こんな感じのメッセージが出てくる。
--> Pushing image done
image: registry.fly.io/pidgey:deployment-01GYQ8NJMBA6RRQPX2YEZY3PJM
image size: 1.5 GB
Provisioning ips for pidgey
Dedicated ipv6: xxxx:xxxx:1::xx:xxxx
Shared ipv4: xx.xxx.xxx.210
Add a dedicated ipv4 with: fly ips allocate-v4
Process groups have changed. This will:
* create 1 "app" machine
No machines in group 'app', launching one new machine
Machine xxxxxx15b97698 [app] update finished: success
Finished launching new machines
Updating existing machines in 'pidgey' with rolling strategy
Finished deploying
image sizega
1.5GBなのは、元にイメージがモリモリだからでしょう。
デプロイが成功しているので、fly status
を実行。
問題なさそう。
最後に、fly open
でブラウザでアクセス。
表示されている!
メモ:
? Create .dockerignore from 1 .gitignore files? No
「.dockerignore作る?」→ 「No」
.dockerignore
について。
こちらの記事によれば、除外したい特定のファイルがある場合に指定する時に利用するとのこと。
秘密情報を含むファイルなど、特定のファイルをデプロイしたくない場合は、.gitignoreではなく、.dockerignoreに記載します。
https://fly.io/docs/getting-started/node/#bonus-points
こっちで記事にしたので、クローズしておく。