まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

タグ:heroku

無料でウェブアプリケーションをデプロイできる数少ない環境となった Heroku ですが、この無料環境内で GitHub 連携による自動デプロイ(いわゆる "GitOps" っぽいデプロイ)を簡単に実現できそうなことがわかりました。手順を以下に紹介しますが、前提条件として以下の環境が揃っていることを事前に確認してください:

(1) Heroku アカウント取得&セットアップ済み
(2) GitHub アカウント取得&セットアップ済み
(3) 自分の PC に git コマンドと Node.js がインストール済み※

※ (3) は手元での動作確認が不要であればインストールされていなくても可


【アプリケーションの準備】
まず自動デプロイを行う対象となるウェブアプリケーションを GitHub に用意します。これは Heroku の dyno で動かせる状態になっていれば何でもいいのですが、以下ではこちらで用意したシンプルなウェブアプリケーションを使って紹介します(自分のアプリを使う場合は、そのアプリを GitHub のリポジトリとして登録してください)。

用意したサンプルはこちらです:
https://github.com/dotnsf/simpleweb

2022020601



内容は後述する動作確認時にも再度紹介しますが、シンプルなメッセージを表示するだけのウェブアプリケーションです:
20220206


このサンプルを使う場合は GitHub リポジトリのページ右上のボタンから "fork" して使ってください:
2022020602


fork すると、用意したサンプルアプリケーションが fork した人のリポジトリとして利用できるようになります。以下では fork 先のリポジトリ URL が https://github.com/yourname/simpleweb となったと仮定して説明を続けます(yourname の部分を自分のものに読み替えてください)。なおパイプライン連携の対象となるリポジトリは public でも private でも以下の作業はできます。

実はこの状態から heroku 側の設定に進むこともできるのですが、このアプリケーションを一度手元で動かしてみることにします(上述の (3) の準備ができていれば可能です。できていない人は読み飛ばしてください)。ターミナルから以下のコマンドを実行して、fork した自分のリポジトリを clone します:
$ git clone https://github.com/yourname/simpleweb

そして依存ライブラリを導入後に node コマンドで実行します:
$ cd simpleweb

$ npm install

$ node app

アプリケーションが起動すると 8080 番ポートで HTTP リクエストを待ち受けます。最後にウェブブラウザで http://localhost:8080/ にアクセスします:
2022020701

↑こんな感じの「ハローワールド!」が表示されれば成功です。機能としてはこれだけの、ごくシンプルなウェブアプリケーションです。このアプリケーションが GitHub のリポジトリに登録された状態になっています。


【heroku 連携】
ではこのアプリケーション(自分で用意したアプリケーションを使う場合はそのアプリケーション)の GitHub リポジトリを heroku のパイプラインやデプロイ先と連携して動くように設定します。

大まかな流れとしては、
1. heroku に(空の)アプリを登録
2. 1. のアプリにデプロイするためのパイプラインを作成
3. GitHub リポジトリとパイプラインを連携(GitHub が変更されたら自動的にパイプラインが実行されるようにする)

のようになります。1. から順に行っていきます。

1. まずは heroku にログインします。heroku アカウント未取得の場合は "Sign Up" リンクから作成することもできます:
https://www.heroku.com/

2022020702


ログインに成功すると以下のような画面になります。過去にアプリケーションを登録したことがあると初期画面にアプリケーション一覧が表示されますが、初めての場合はアプリケーションは表示されず、このような登録画面になります:
2022020703


では先程 GitHub に用意(fork)したアプリケーションが heroku 上で動かすことができるように登録します。画面右上のボタンから "New" - "Create new app" を選択します:
2022020704


そしてアプリの名前とデプロイ先リージョンを指定します。名前は他に使われていないものを指定する必要があります(下図では "dotnsf-simpleweb" という名称を指定しています)。リージョンは(無料版の場合は)United States か Europe のどちらかを選択します。単にアプリを作るだけならここで "Create app" ボタンを押せばいいのですが、今回はアプリと同時にパイプラインも作ってしまいましょう。というわけで "Add to pipeline" ボタンをクリックします:
2022020705


2. 続けてパイプラインの作成を行います。上の続きで "Add to pipeline" ボタンをクリックすると "Choose a pipeline" という選択エリアが現れるので、ここで "+ Create new pipeline" を選択します(このアプリ用の新しいパイプラインを作る、という意味です):
2022020706


するとパイプラインの名前(下図では "dotnsf-simpleweb-pipeline" としています)と、デプロイ先ステージを指定する画面が現れます。名前は好きな名前でいいのですが、デプロイ先ステージは "staging"(検証用) か "production" (本番用)かを選択します。下図ではそのまま本番環境にデプロイする想定で "production" を選択しています※。 最後に "Create app" ボタンをクリックします:
2022020707

※別のアプリケーションをもう1つ作って staging に指定し、GitHub → Staging(検証用アプリ) → Production(本番運用アプリ) というパイプラインを作ることもできますが、今回は割愛します。


するとこのような画面になります。ここまでで 1. のアプリにデプロイするための 2. のパイプラインができあがりました:
2022020708


そして最後の 3. の連携設定を行います。「GitHub のリポジトリが更新された」というタイミングに合わせて、同リポジトリの main ブランチの内容をパイプラインで指定されたアプリにデプロイする、というものです。

まず同じブラウザで GitHub にログインしておきましょう。GitHub のセッションがある状態ですすめることで面倒な認証部分を省略できます:
https://github.com/

そして先程の画面の続きです。まずはデプロイ方法として "GitHub" と書かれたアイコンをクリックします:
2022020701


すると画面下部が変わり、"Connect to Github" と書かれたボタンが現れます。このボタンをクリックします:
2022020702


するとどのリポジトリと連携するかを指定する画面に切り替わります。上部で GitHub へのログインを済ませていれば、後は自分のリポジトリを指定するだけです。自分の GitHub ID が指定されている状態で、対象リポジトリ(fork したリポジトリ)を指定するので "simpleweb" と入力して "Search" ボタンをクリックします:
2022020703


すると自分のリポジトリ内の simpleweb リポジトリ(fork したリポジトリ)が見つかります。その横の "Connect" ボタンをクリック:
2022020704


ここまでの作業でパイプラインと GitHub の simpleweb リポジトリとがつながりました。後は対象ブランチを選択しての自動デプロイ機能の設定だけです:
2022020705


画面下にスクロールすると、GitHub リポジトリのどのブランチからアプリケーションをビルドして自動デプロイするか、という選択画面になります。今回は main ブランチをそのまま使うことにして(つまり main ブランチに直接変更を加えることにして)"main" を指定したまま "Enable Automatic Deploys" ボタンをクリックします:
2022020706


すると画面の表示内容が切り替わり、main ブランチからの自動デプロイが有効になったことがわかります。これで事前準備が整いました:
2022020707


ちなみに、この時点で heroku のパイプライン画面を表示すると、このように GitHub から連携したパイプラインが自分の simpleweb アプリ(Production)につながっている様子が確認できます:
2022020705



【動作確認】
ではここまで設定した内容が正しく動作することを確認してみます。ある程度 git コマンドの操作がわかる人であれば、ローカルリポジトリの内容を変更して、git add して、git commit して、git push する・・・という一連の流れを実施いただいてもいいのですが、今回はウェブ画面だけで同じことを行ってみます。

改めて GitHub の(自分の)デプロイ対象リポジトリ(https://github.com/yourname/simpleweb)をウェブブラウザで開きます:
2022020701


ここに変更を加えます。見た目で変更がわかりやすいのは web/ フォルダ以下にファイルを追加したり、既存の index.html を変更したりすることですが、今回は後者でいきましょう。web/ フォルダを選択後に index.html ファイルを選択し、画面右上の鉛筆マークをクリックして編集画面に切り替えます:
2022020702


index.html をブラウザで編集する画面に切り替わるので、何か変更を加えてみます。以下の例では見た目にもわかりやすそうな所で、26 行目の「ハローワールド!」と表示されていた部分を「ハロー heroku ワールド!」と書き換えてみました:
2022020703


画面を下にスクロールすると、git commit のオプション入力画面になります。コミットメッセージ(下の例では "index.html メッセージ変更")を入力し、対象ブランチはデフォルトの "Commit directly to the main branch" が選択されたままにします。最後に "Commit changes" ボタンをクリックして git commit します(サーバー側を直接 git commit するので、 git push は不要です):
2022020704


コミットしました:
2022020706


先程のパイプライン画面を表示すると、GitHub リポジトリのコミットが行われたことを検知して、パイプラインが稼働している様子が確認できます(シンプルなパイプラインなのですぐに稼働後、つまりデプロイ後の状態に切り替わってしまいます・・。下図はデプロイ後の画面です):
2022020707


では heroku 上にデプロイされた変更後のアプリケーションを確認してみます。https://(アプリ名).herokuapp.com/ を開くか、またはパイプライン画面右下の "Open app" と書かれた箇所をクリックして、heroku 上のアプリケーションをウェブブラウザで開きます:
2022020707


heroku 上で稼働しているアプリケーションが開き、先程加えた変更が有効になっていることを確認します:
2022020708


これで GitHub リポジトリと連携した heroku デプロイ用パイプラインが作れて、かつ自動実行も有効になり、GitHub リポジトリに変更が加わればウェブアプリケーションにも反映される、という仕組みの構築ができました。

現実的にはステージング環境やローカル環境で動作確認してから反映、という流れになると思いますが、もちろんその方法(手動で git コマンドを使ってコミット&プッシュ)でも自動的に新しいアプリケーションに切り替えることもできます。またそもそもの Git ブランチ戦略をどうするか?という課題も出てくると思っています。

一方で、CLI を使わなくても(Git リポジトリをウェブで更新さえすれば)公開アプリケーションの更新ができるので、普段 CLI を使わないような、あまり詳しくない人でも(直接アプリケーションの挙動に影響しない範囲での変更であれば)コンテンツの変更管理ができる仕組みが構築できたと思っています。こういう仕組みが簡単に作れてしまう点は heroku の強みだと思います。

Bonsai Elasticsearch はその名前の通り、高速多機能な検索エンジンである Elasticsearch による検索機能をダッシュボード機能も含めてマネージドサービスとして提供するプロバイダーです。日本語の情報が少ないせいか、技術記事含めて情報自体を見ることがあまりありませんでした。偉そうに言ってますが、自分も以下の件で興味を持つまでは存じ上げていませんでした:
2021083002


Bonsai Elasticsearch は Salesforce から提供されている PaaS 環境である herokuサードパーティサービスの1つでもあります。heroku 自体も無料で使える利用枠が提供されていて、また Bonsai Elasticsearch にも無料枠があるため、heroku を経由してサービスを申し込むと「検索エンジンの使えるアプリケーションを無料で開発・運用できる環境」を手に入れることができます。世に多くのクラウド環境はありますが、Elasticsearch が無料で使えるアプリケーション・サーバー環境、という時点でかなり珍しいといえます:
2021083003


一方で、現実的に日本人を対象とするアプリケーションを考えると、検索機能は日本語検索ができなければあまり実用的とはいえません。英語のように単語と単語の間に明確なスペースが入って区切られるわけではない日本語は、テキスト内の単語と単語の区切りを見つける時点でかなりハードルが高く、そこから更に検索インデックスを作る必要があるためです。ここまでの機能がサポートされていないと検索エンジンとしては使いにくいのでした。 例えば自分で自分の(手元の)PC やサーバーに Elasticsearch をインストールした場合であれば、自分で更に日本語形態素解析機能を追加でインストールすればいいのですが、クラウドのマネージドサービスとして提供されている場合、そのような権限をもらえないことも多いため、サービスとして提供されている機能の中に多言語対応(日本語対応)が含まれていないといけない、という高めのハードルが設定されているのでした。

そんな中で見つけたこの Bonsai Elasticsearch 。ネットの日本語情報が少ないということは、日本語が使えないということかな・・・ と勝手に想像していました。 が、Bonsai のドキュメントを調べてみると Bonsai Elasticsearch に始めから導入されているプラグイン一覧の中に日本語形態素解析である "Kuromoji Analyzer" の文字を見つけました。あれ?これはもしかして期待できるやつ?:
https://docs.bonsai.io/article/135-plugins-on-bonsai

2021083001


というわけで、実際に Bonsai Elasticsearch で日本語検索ができるかどうかを調べてみました。結論としてはどうやら使えそう(!!!)だと思ってます。以下はその際の記録です。


【調査に使ったシナリオ】
最初に迷ったのは「何を調べれば日本語検索ができるといえそうか」でした。日本語データを入れて、日本語で検索して、日本語のそれっぽいデータが返ってきたらいいのか?? というと、そうともいえないし・・・

で、今回は Elastic 社の日本語エンジニアリングブログから提供されていた『Elasticsearchで日本語のサジェストの機能を実装する』というエントリで紹介されていた方法が Bonsai Elasticsearch でできるかどうかを調べました。詳細はリンク先を確認していただきたいのですが、大まかな内容としては kuromoji を使う前提で日本語でのサジェスト機能を実装するためのインデックスおよびマッピングを作成し、日本語のデータを入れた上で検索して結果を見る、というものです。ここで紹介されているのは入力ミスまでを考慮した曖昧な検索を行うという内容で、この結果が期待通りになればそれはもう大丈夫でしょ、という判断です。

ちなみに同ページで紹介されている内容自体が日本語サジェストを実現するための考え方なども紹介されていて非常に有用でした。その上で、ここで紹介されていることと同じ内容が Bonsai Elasticsearch に対して行っても同じ結果になるか(Bonsai Elasticsearch で日本語形態素解析が使えれば同じ結果になるはず)、を試してみました:
https://www.elastic.co/jp/blog/implementing-japanese-autocomplete-suggestions-in-elasticsearch


【Bonsai Elasticsearch の準備】
まずは Bonsai Elasticsearch のインスタンスを準備します。自分の場合は heroku 経由でインスタンスを作成したので、その場合の手順を紹介します。

まず heroku で無料アカウントを作成し、クレジットカードを登録しておきます(無料版の Bonsai Elasticsearch を使いますが、そのためには heroku アカウントにクレジットカードの登録が必要です)。改めてブラウザで heroku にログインし、(必要であれば)アプリケーションを1つ作成した上でそのアプリケーションにアドオンとして紐付ける形で Bonsai Elasticsearch を追加します。アプリケーションを選択して、"Overview" タブから "Configure Add-ons" を選択します:
2021083004


アドオンを選択する画面で検索ボックスに "Bonsai" と入力すると "Bonsai Elasticsearch" が見つかるのでこれを選択します:
2021083005


こんな感じの確認画面が表示されたら、無料プランの "Sandbox -  Free" が選択されていることを確認して(これ以外は有料です) "Submit Order Form" ボタンをクリックします:
2021083006


正しく処理されると、アプリケーションのアドオン一覧に "Bonsai Elasticsearch" が追加されます:
2021083007


追加された Bonsai Elasticsearch にアクセスするための接続 URL を確認します。同アプリケーションの "Settings" タブから "Reveal Config Vars" ボタンをクリックします:
2021083008


すると、このアプリケーションの動作時に設定される環境変数の一覧が表示されます。さきほど、Bonsai Elasticsearch を追加した際のオペレーションで "BONSAI_URL" という環境変数が設定されているはずです:
2021083009


ここでコピーした BONSAI_URL の値は、このようなフォーマットのテキストになっているはずです:
https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443

この値をこの後のインデックス作成やクエリー実行時に使うことになります。何度も使うことになるのでコピペできるよう、どこかに退避しておきましょう。

ここまで完了すれば Bonsai Elasticsearch の準備は環境です。


【Bonsai Elasticsearch に日本語インデックスと日本語データを作成して検索】
ここまでの準備ができれば Elastic 社の日本語エンジニアリングブログで紹介されていた、この内容を実際に試すことができるようになります:
https://www.elastic.co/jp/blog/implementing-japanese-autocomplete-suggestions-in-elasticsearch

ただ具体的なコマンド入力を考慮すると、このままだと少しわかりにくいため、この内容がもう少し試しやすくなるようなファイルやコマンド集(なんなら実行結果も含まれてますw)を作って公開しました。単に挙動や結果だけを確認したい人はこちらをダウンロードして使ってください:
https://github.com/dotnsf/bonsai_elasticsearch


以下に curl を使った具体的な手順とともに紹介します。日本語エンジニアリングブログによると、最初に kuromoji を使った日本語インデックスを作成する必要があります。上述でダウンロードしたファイルの中に含まれている my_suggest.json が日本語インデックスとマッピングを構成するファイルなので、以下の curl コマンドを実行してこれを Bonsai Elasticsearch に PUT します:
$ curl -XPUT https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest -d @my_suggest.json -H 'Content-Type: application/json'

https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443 部分は先述の BONSAI_URL の値に置き換えて指定してください。以下同様)

次に検索対象となる日本語データをまとめてバルクアップロードします。データファイルは japanese.json です(厳密には JSON フォーマットではなく、複数の JSON が繋がっているフォーマットです。そのため後述のコマンドで特殊な Content-Type を指定する必要があります)。このファイルを以下の curl コマンドで Bonsai Elasticsearch に POST します:
$ curl -XPOST https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_bulk --data-binary @japanese.json -H 'Content-Type: application/x-ndjson'

(Content-Type の指定値が 'application/json' ではなく、'application/x-ndjson' となっている点に注意してください)

これで Bonsai Elasticsearch に日本語インデックスと日本語データが格納されました。これらが正しく日本語で検索できるかどうか(このブログの本来の目的でいえば、5種類の曖昧な日本語検索をしても同じ結果になるかどうか)を確認してみます。5種類の検索クエリーを5つのファイル(query1.json, query2.json, ..., query5.json)で用意したので、5回に分けてそれぞれを実行し、その結果を result1.json, result2.json, ..., result5.json に格納します:
$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query1.json -H 'Content-Type: application/json' > result1.json

$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query2.json -H 'Content-Type: application/json' > result2.json

$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query3.json -H 'Content-Type: application/json' > result3.json

$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query4.json -H 'Content-Type: application/json' > result4.json

$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query5.json -H 'Content-Type: application/json' > result5.json

ちなみにこれらの検索の内容ですが、「日本」という文字を多く含む日本語データがある程度格納されている状況下で、query1.json では「日本」を、query2.json では「にほn」を、query3.json では「にhん」を、query4.json では「にっほん」を、そして query5.json では「日本ん」を検索します。このように入力ミスまで考慮した曖昧な検索をしても同じ検索結果になるようなインデックスとマッピングを作る、という内容でした。実際に result1.json から result5.json まで見ると、検索結果はどれも一致しているはずです。


・・・というわけで、無料の Bonsai Elasticsearch (と導入済みの kuromoji)を使って日本語インデックスで日本語データを期待通りに検索できることがわかりました。こりゃ、いいモン見つけたかも!



このページのトップヘ