fly.ioの「Deploy Your Application via Dockerfile」を試してみる（Bolt for Pythonを使ったSlackアプリ）

**秋の田のかりほの庵の苫を荒みわがころも手は露に濡れつつ**

Advent Calendar 2022 50日目[^1]の記事です。
I'm looking forward to 12/25,2022 :santa::santa_tone1::santa_tone2::santa_tone3::santa_tone4::santa_tone5:
私の[Advent Calendar 2022 一覧](https://docs.google.com/spreadsheets/d/1HQvFjagQLRPjOYAjDVzWp9S4b8dKixxvvaz_TtbZWto/edit#gid=1723448955)。

[^1]: @kaizen_nagoya さんの「[「@e99h2121 アドベントカレンダーではありますまいか Advent Calendar 2020」の改訂版ではありますまいか Advent Calendar 2022 １日目　Most Breakthrough Generator](https://qiita.com/kaizen_nagoya/items/49ebebee3a0377f3b59b)」から着想を得て、模倣いたしました。 

---

# はじめに

[fly.io](https://fly.io/)の話を書きます。
はじめて使ってみました。

ちょっと変わり種？　の
「[Deploy Your Application via Dockerfile](https://fly.io/docs/getting-started/dockerfile/)」の例を示します。


# What's [fly.io](https://fly.io/) ?

> Run your full stack apps (and databases!) all over the world. No ops required.

PaaSに分類されるサービスだとおもいます。
軽く使ってみたくらいですが、使いやすいです。
[Phoenix](https://www.phoenixframework.org/)アプリとの相性が抜群によさそうです。

## [Phoenix](https://www.phoenixframework.org/)アプリをデプロイする例

@koga1020 さんの記事をオススメしておきます。

https://zenn.dev/koga1020/articles/b60725143bbadf6a2b5a

---

# [Deploy Your Application via Dockerfile](https://fly.io/docs/getting-started/dockerfile/)

題材はPythonを使ったSlackアプリにします。

https://slack.dev/bolt-python/ja-jp/tutorial/getting-started

「[Bolt 入門ガイド](https://slack.dev/bolt-python/ja-jp/tutorial/getting-started)」で紹介されているプログラムをそのまま使います。
Slack側の設定は、[Bolt 入門ガイド](https://slack.dev/bolt-python/ja-jp/tutorial/getting-started)の通りに設定しているものとします。

## ソースコード

以下を用意します。
こちらのPythonのプログラムは、Slackアプリが追加されたチャンネルで`hello`という文字列を含む書き込みがされると、`Hey there <@投稿者>　!`というふうに挨拶を返してくれるbotです。
「[Bolt 入門ガイド](https://slack.dev/bolt-python/ja-jp/tutorial/getting-started)」の通りのソースコードです。

```python:app.py
import os
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

# ボットトークンとソケットモードハンドラーを使ってアプリを初期化します
app = App(token=os.environ.get("SLACK_BOT_TOKEN"))

# 'hello' を含むメッセージをリッスンします
# 指定可能なリスナーのメソッド引数の一覧は以下のモジュールドキュメントを参考にしてください：
# https://slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html
@app.message("hello")
def message_hello(message, say):
    # イベントがトリガーされたチャンネルへ say() でメッセージを送信します
    say(f"Hey there <@{message['user']}>!")

# アプリを起動します
if __name__ == "__main__":
    SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()
```

同じフォルダにDockerfileを用意します。

```Dockerfile:Dockerfile
FROM python:3

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD [ "python", "./app.py" ]
```

## アカウント作成

私は、 https://fly.io/ サイト上でつくりました。
GitHubアカウントで連携しました。

## Installing flyctl

https://fly.io/docs/getting-started/installing-flyctl/

に従って、インストールをして`flyctl`コマンドが使えるようにします。

## `flyctl`コマンドでの操作

`flyctl`コマンドで操作していきます。

### ログイン

まずはログインします。

```
$ flyctl auth login 
```

### 初期設定

```
$ flyctl launch
```

`? Overwrite "/Users/awesome/first-bolt-app/Dockerfile"? (y/N) `には`N`を答えます。
`? Would you like to setup a Postgresql database now?`には`N`を答えます。
そのほかの設問は、デフォルトのままエンターでよいでしょう。
お好みで変更してください。
以下のような`fly.toml`ファイルが自動的につくられます。

```toml:fly.toml
# fly.toml file generated for rough-paper-5532 on 2022-02-19T23:03:26+09:00

app = "rough-paper-5532"

kill_signal = "SIGINT"
kill_timeout = 5
processes = []

[env]
  PORT = "8080"

[experimental]
  allowed_public_ports = []
  auto_rollback = true

[[services]]
  http_checks = []
  internal_port = 8080
  processes = ["app"]
  protocol = "tcp"
  script_checks = []

  [services.concurrency]
    hard_limit = 25
    soft_limit = 20
    type = "connections"

  [[services.ports]]
    handlers = ["http"]
    port = 80

  [[services.ports]]
    handlers = ["tls", "http"]
    port = 443

  [[services.tcp_checks]]
    grace_period = "1s"
    interval = "15s"
    restart_limit = 0
    timeout = "2s"

[[statics]]
  guest_path = "/app/public"
  url_prefix = "/static/"
```

このアプリでは、ソケットモードで動作させますので不要な設定は消しておきます。
これをしないと、次回のデプロイ時にヘルスチェックに引っかかるなどして、新しいほうがうまくデプロイされないなどの動きになります。

```toml:fly.toml
# fly.toml file generated for rough-paper-5532 on 2022-02-19T23:03:26+09:00

app = "rough-paper-5532"

kill_signal = "SIGINT"
kill_timeout = 5
processes = []
```


## 環境変数の設定

環境変数を２つ設定しておきます。

- `SLACK_BOT_TOKEN`
- `SLACK_APP_TOKEN`

それぞれの環境変数の意味は、「[Bolt 入門ガイド](https://slack.dev/bolt-python/ja-jp/tutorial/getting-started)」をご参照ください。
このアプリ特有の環境変数です。


```
$ flyctl --app rough-paper-5532 secrets set SLACK_BOT_TOKEN="xoxb-xxx"
$ flyctl --app rough-paper-5532 secrets set SLACK_APP_TOKEN="xapp-xxx"
```

`--app rough-paper-5532`の部分は、ご自身のアプリ名で置き換えてください。


## deploy :rocket:

いよいよデプロイタイムです。
Dockerをローカルで動かしておいてください。

> If you have Docker running locally, it builds it on your machine. If not, it builds it on a Fly build machine. 

ローカルマシンでビルドして、[fly.io](https://fly.io/)にイメージをPushしているとのことです。

```
$ flyctl deploy --detach
```

`--detach`フラグの意味は

> Return immediately instead of monitoring deployment progress

です。
ヘルプは、`flyctl deploy --help`で確認できます。


---

# Wrapping up :lgtm::lgtm::lgtm::lgtm::lgtm:

[fly.io](https://fly.io/)のドキュメントの中から、ちょっと変わり種？　の「[Deploy Your Application via Dockerfile](https://fly.io/docs/getting-started/dockerfile/)」の例を示しました。
今回はPythonで作ったSlackアプリにしましたが、[Elixir](https://elixir-lang.org/)の[Bandit](https://github.com/mtrudel/bandit)を利用して作ったSlackアプリ、LINEアプリを同じ要領で動かせるとおもいます。
今後やってみます。


## 追伸

:::note warn
以下は、ハマったこととしてそのまま残しておきます。

:::

:::note
すでにfly.tomlを書き換えることで解決することは確認しておりまして、記事本文には反映済みです。
:::


`app.py`を変更して、`flyctl deploy`すると変更内容が[fly.io](https://fly.io/)に飛んでいるのですが、以下の挙動を確認しました。

- たまに古いDockerコンテナのほうがまだ動いている
- しばらくそのまま待っていると、新しいDockerコンテナのほうが完全に止まって、古いDockerコンテナのほうの動きをする


```
$ flyctl status

Instances
ID      	PROCESS	VERSION	REGION	DESIRED	STATUS 	HEALTH CHECKS      	RESTARTS	CREATED   
dccb1dc2	app    	6 ⇡    	sin(B)	run    	running	1 total, 1 critical	0       	2m31s ago	
3af1fa4b	app    	2      	nrt   	run    	running	1 total, 1 critical	1       	35m8s ago
```

一時的に新らしいコンテナと古いコンテナが動いているようです。
`VERSION 6`のほうに切り替わってくれればいいのですが、このまましばらく待つと、`VERSION 6`がいなくなります。

回避策は、適当な環境変数を追加することです。

```
$ flyctl --app rough-paper-5532 secrets set HOGE="hoge"
```

一応、これで最新のほうの動きに寄りました :tada::tada::tada:


## 追伸の追伸 ーー [Troubleshooting your Deployment](https://fly.io/docs/getting-started/troubleshooting/)

[Troubleshooting your Deployment](https://fly.io/docs/getting-started/troubleshooting/)を読んでみました。
なんとなくですが、ヘルスチェックに引っかかって新しいほうが停止されていたようです。
このアプリはhttp(s)は使っていないので不要な設定を`fly.toml`から消しておきます。
これで、`fly deploy`で最新内容がうまくデプロイされるようになりました。


```toml:fly.toml
# fly.toml file generated for rough-paper-5532 on 2022-02-19T23:03:26+09:00

app = "rough-paper-5532"

kill_signal = "SIGINT"
kill_timeout = 5
processes = []
```

ヨシヨシ、知見がたまった。

---