【fly.io】愛を込めて花束を


タイトルは Superfly名曲ですが、本記事はネタ記事でもなく釣り記事でもなく、もちろんポエム記事でもありません。node の PaaS 環境である fly.io の真面目なお話です。

え?なぜこのタイトルかって?

 fly.io
  ↓
 フライ.アイオー
  ↓
 Super fly 愛を 込めて…

ハイ!Merry Christmas!🎅  Σd(゚∀゚d) オゥイェ!!!

これはなに?

fly.io は、node.js に特化した PaaS で独自の CDN を持ち Edge サーバによるキャッシュで高速なレスポンスを提供するフルマネージドなサービスです。AWS にも Lambda というサービスがあり、それを CloudFront に乗せた [email protected] というのがありますが、それとほぼ同列です。もっと簡単に言うと node.js に特化した heroku って感じです。Lambda は AWS のサービスですので面倒な AWS の契約やロールの管理が必要ですが、fly.io は無料アカウントを作成すればすぐに使用可能な分とてもお手軽です。

また、fly.io ではローカルで動作するサーバがオープンソースで提供されています。これによって手元で開発、動作確認を行い、手軽にデプロイすることが可能です。

詳しくは公式サイトをご覧ください。随所に挿入されているイラストがゆるかわいいですね!

https://fly.io/

料金

fly.io は従量課金制ですが、毎月、最初の $10 までは無料で利用できます。
気になる金額ですが、TCP セッション時間に対しての課金で下記の金額がベースとなります。

$0.000000878 per connection, per second

24時間30日間ずっと接続が発生したとして、

0.000000878 * 24 * 60 * 60 * 30 = 2.275776 ドル

なので全然無料枠で遊べますね。
ただ、これは1コネクションでの話ですし、その他、帯域やCPUグレードなどで金額が変わり、実運用ではそうはいきませんが、少なくともカード登録不要でサインアップできるので、使いすぎて勝手に課金というのは無さそうです。(ただし自己責任で)

金額詳細はこちらから。
https://fly.io/docs/pricing/

登録

サインアップは下記URLから行えます。
https://fly.io/app/sign-up
私は GitHub のアカウントから登録したので30秒くらいでサインインできました。

後述のコマンドラインでの認証があるので、パスワードは設定しておきましょう。

インストール

fly.io 上のランタイムサーバは fly コマンドをインストールする事でローカルでシミュレートできます。fly コマンドではランタイムサーバの起動の他に、デプロイやログの確認、テストの実行なども行えます。

Mac だと Homebrew でパッケージが公開されていますので下記コマンドでインストールできます。(リポジトリ名がSuperfly!)

$ brew tap superfly/brew && brew install superfly/brew/fly

もちろん npm でも提供されているので、

$ npm install -g @fly/fly

でも OK です。

fly コマンドを実行し下記のようにヘルプが表示されれば成功です。

$ fly
Fly edge application runtime

VERSION
  @fly/cli/0.54.5 darwin-x64 node-v10.18.0

USAGE
  $ fly [COMMAND]

COMMANDS
  apps       list your apps
  build      Build your local Fly app
  deploy     Deploy your local Fly app
  help       display help for fly
  hostnames  list hostnames for an app
  login      login to fly
  logs       logs for an app
  new        create a new app
  orgs       list your organizations
  releases   list releases for an app
  secrets    manage app secrets
  server     run the local fly development server
  test       run unit tests

サンプル

さて何か作って動かしたいのですが、気の利いたサンプルも思いつかないので、休日を取得する node パッケージ @holiday-jp/holiday_jp を使用して、指定期間内の休日を JSON で返す API をサクッと作成してみましょう。

エンドポイントに from と to を GET で渡すと、

[
  {
    "date": "2020-01-01T00:00:00.000Z",
    "week": "水",
    "week_en": "Wednesday",
    "name": "元日",
    "name_en": "New Year's Day"
  },
  {
    "date": "2020-01-13T00:00:00.000Z",
    "week": "月",
    "week_en": "Monday",
    "name": "成人の日",
    "name_en": "Coming of Age Day"
  }
]

という JSON が返るだけのクソしょうもないAPI です。

fly.io アプリの作成手順

それではアプリを作成しましょう。先ほどインストールした fly コマンドをベースに作業します。

ログイン (fly login)

まずはログインしてアクセストークンをローカルに保存しましょう。
これによってアプリの作成やデプロイの度に毎回認証をする事がなくスムーズに作業が行えます。

$ fly login
email: [email protected] # 登録時のメールアドレス
password: ********** # パスワード
2FA code (if any) [n/a]: n    # 2ファクタ認証を有効にしている場合は y
Wrote credentials at: ~/.fly/credentials.yml

アクセストークンはホームディレクトリに保存されるので、最初の1回のみの実行で良いです。

アプリの作成 (fly apps:create)

アプリを新規作成します。fly.io のサーバ側にも登録が必要ですが、コマンドを実行する事でサーバ側の登録とローカルのセットアップも同時に行ってくれます。

最初にアプリの名称を入力しますが、アプリ名称がサブドメインとなり公開される (アプリ名称.edgeapp.net) ため、fly.io 全体でユニークな名前(S3のバケット名みたいな感じ)でなければいけません。とりあえず作ってみる場合はブランクとしてエンターキーを押すと超適当な名前を付けてくれます。

$ fly apps:create
{ data: { organizations: { nodes: [Array] } } }
? app name (leave blank to use a random name) #アプリ名称を入力
? select an organization #所属する fly.io 上の組織を選択。通常は1つ。
creating app... done
Created a new app: green-sky-8879
--> green-sky-8879.edgeapp.net
{ data:
   { createApp:
      { app:
         { id: 'green-sky-8879',
           name: 'green-sky-8879',
           runtime: 'NODEPROXY',
           appUrl: 'green-sky-8879.edgeapp.net' } } } }
Created a .fly.yml for you.

今回は、green-sky-8879 という素敵なアプリ名を付けてもらいました。
これで、https://green-sky-8879.edgeapp.net のドメインでの公開が準備できました。

スクリプトの記述

fly.io では npm パッケージをバンドルしてデプロイすることも可能なので、適宜 npm install したパッケージを import して、、、と、普通の node.js アプリケーションと同じようにコードを書きます。

今回は @holiday-jp/holiday_jp と moment の node パッケージを使用します。いつも通り npm コマンドでパッケージをインストールします。

$ npm install --save @holiday-jp/holiday_jp moment 

次に index.js にメインとなる処理を記述します。flyランタイムには予め用意されたAPIがあるのでそれらを使用してレスポンスを返します。

import holiday_jp from '@holiday-jp/holiday_jp'
import moment from 'moment'

fly.http.respondWith(async (req) => {
  const url = new URL(req.url)
  const from = new Date(url.searchParams.get('from'))
  const to = new Date(url.searchParams.get('to'))

  const holidays = holiday_jp.between(from, to)

  return new Response(JSON.stringify(holidays), { status: 404, contentType: 'application/json' })
})

GET で from と to を取得して、ライブラリ経由で休日を返すだけのコードです。

ローカルサーバの起動 (fly server)

ではローカルで fly.io のランタイムを実行して、ローカルサーバを立ち上げてみましょう。

$ fly server
Using ~/green-sky-8879 as working directory.
Generating Webpack config...
new runtime, app: ~/green-sky-8879
v8 snapshots enabled
Memory Cache Adapter: MemoryCacheStore
Blob Cache Adapter: Disk [path:/var/folders/0v/000000000000_qh000000000000000gn/T/fly-blobcache]
Compiled app in 721ms (source: 800.95KB sourceMap: 963.95KB hash: fa48f4b7f12d34f6883d048ef28eee147f84ee6a)
Server listening on :::3000
Updating app in local runtime...

これだけででローカルホストの 3000 ポートでアプリが起動しました。
なんだかメモリキャッシュとかエッジサーバっぽい感じのログが出てますね!

試しに curl でアクセスしてみましょう。

$ curl "http://localhost:3000/?from=2020-01-01&to=2020-01-31"
[
  {
    "date": "2020-01-01T00:00:00.000Z",
    "week": "水",
    "week_en": "Wednesday",
    "name": "元日",
    "name_en": "New Year's Day"
  },
  {
    "date": "2020-01-13T00:00:00.000Z",
    "week": "月",
    "week_en": "Monday",
    "name": "成人の日",
    "name_en": "Coming of Age Day"
  }
]

このように JSON が返ってくれば成功です!
fly server はライブリロード対応なので、コードの修正も即座に検知してビルドし直してくれます。
開発中は常に立ち上げておくと良いでしょう。

デプロイ (fly deploy)

では今度はこれを fly.io 上、つまりインターネット上に公開してみましょう。
デプロイは fly deploy コマンドで一瞬です。

$ fly deploy
Deploying green-sky-8879 (env: production)
Generating Webpack config...
Compiled app in 562ms (source: 800.95KB sourceMap: 963.95KB hash: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
Deploying v1 globally @ https://green-sky-8879.edgeapp.net
App should be updated in a few seconds.

公開されました!
先ほどはローカルホストの3000ポートでしたが、green-sky-8879.edgeapp.net のドメインで公開されました。curl でアクセスしてみましょう。

$ curl "https://green-sky-8879.edgeapp.net?from=2020-01-01&to=2020-01-31"
[
  {
    "date": "2020-01-01T00:00:00.000Z",
    "week": "水",
    "week_en": "Wednesday",
    "name": "元日",
    "name_en": "New Year's Day"
  },
  {
    "date": "2020-01-13T00:00:00.000Z",
    "week": "月",
    "week_en": "Monday",
    "name": "成人の日",
    "name_en": "Coming of Age Day"
  }
]

先ほどの JSON と同じ結果が返れば成功です!

まとめ

ちょっと駆け足になりましたが、今回は、ただ単に node の処理を fly.io 上に置いただけなので、何がすごいのかイマイチ伝わり辛かったと思います。サーバサイドレンダリングやもっと重い処理などを効率的にエッジサーバにキャッシュさせる事で fly.io の真価を発揮するので、時間があれば画像のリサイズ処理とか、mermaid.js でグラフ SVG をサーバサイドで生成とかやってみたいですね。
まだまだ情報も少ないので、皆さんも是非さわってレポートしてみて下さい。

みんなで flyer になりましょう!

私事ですが

本当に私事で恐縮ですが、私が qnote の CTO として参加するアドベントカレンダーは今年が最後です。
退職や引退ではなく、これからも現役で頑張っていきたいと考えた結果、学ぶ側の視点にスイッチし、若い世代にバトンを渡す、という結論に至りました。
来年からは若手が中心となってさらに技術面を引っ張っていってくれる事でしょう!

また、弊社は来年移転を予定しています。
人も一気に増え、いろんなことにチャレンジしようとしていますので、
今後とも猫会社qnoteをよろしくお願い致します!

それでは皆さん良いお年を!

参考