Closed10

Fly.ioへGleamアプリをデプロイ

MzRyuKaMzRyuKa

なお、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
MzRyuKaMzRyuKa

「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へ修正。

https://gleam.run/news/v0.27-hello-panic-goodbye-try/

(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()に変更している。

ちなみに、ソースコードとしては以下が該当。

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.と警告を出してくるのでわざと残してある。

MzRyuKaMzRyuKa

さて、実行。

$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!
MzRyuKaMzRyuKa

デプロイ準備。

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

MzRyuKaMzRyuKa

Dockerファイルの作成。
ファイルの置き場所は、pidgeyディレクトリ直下

Dockerfile
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"]
MzRyuKaMzRyuKa

デプロイコマンドの実行。

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」

でデプロイ開始。

MzRyuKaMzRyuKa

デプロイ中

デプロイが終わったら、こんな感じのメッセージが出てくる。

--> 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でブラウザでアクセス。
表示されている!

MzRyuKaMzRyuKa

メモ:

? Create .dockerignore from 1 .gitignore files? No
「.dockerignore作る?」→ 「No」

.dockerignoreについて。

こちらの記事によれば、除外したい特定のファイルがある場合に指定する時に利用するとのこと。

秘密情報を含むファイルなど、特定のファイルをデプロイしたくない場合は、.gitignoreではなく、.dockerignoreに記載します。
https://fly.io/docs/getting-started/node/#bonus-points

https://bel-itigo.com/migrate-from-heroku-to-flyio/

このスクラップは2023/05/04にクローズされました