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

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

このブログエントリは IBM Cloud アドベントカレンダー 2023 に参加しています(西川さん、いつもありがとうございます!)。12/04 ぶんの記事です:
2023112901



IBM Cloud から提供されている生成 AI のプラットフォーム(および API )の watsonx.ai を使って英会話練習アプリを作って、公開してみました:
https://github.com/dotnsf/wwx

2023112902



【使うための準備】
アプリケーションを動かすには以下が必要です(IBM Cloud 上での手続きが必要ですが、フリープランを使って無料枠内で運用するぶんには料金はかかりません):
Git および Node.js がインストールされ、インターネットに繋がる PC(Windows, MacOS, Linux, ..)と Chrome ブラウザ
IBM Cloud のアカウント
・IBM Cloud の API キー
・IBM Cloud 内で Watson Studio および Watson Machine Learning のインスタンスをダラスのデータセンターで有効化
・IBM Watsonx.AI のプロンプトラボを使った時のプロジェクト ID


以前に watsonx.ai の REST API を紹介した記事を書きました。Watson Studio や Watson Machine Learning を有効にする手順や、API キー、プロジェクト ID を取得する手順についてはこの記事内でも紹介しているので必要に応じて参照してください:
https://dotnsf.blog.jp/archives/1082246988.html



【アプリケーションのセットアップ】
では実際に英会話アプリケーションを使うためのセットアップを行います。まずはコマンドプロンプトやターミナルを開いて、git でソースコードをクローン(ダウンロード)します:
$ git clone https://github.com/dotnsf/wwx

ダウンロードしたソースコードの依存ライブラリをインストールします:
$ cd wwx

$ npm install

テキストエディタで .env ファイルを開き、あらかじめ用意してある API キープロジェクト ID の内容をそれぞれ以下の場所に(コピー&ペーストするなどして)記述し保存します。他の部分は変更不要です。イコール記号の前後や行頭・行末に余計な空白などが含まれてないように注意してください:
WATSONX_API_KEY=(API キーの値)
WATSONX_PROJECT_ID=(プロジェクト ID の値)
WATSONX_MODEL_ID=meta-llama/llama-2-70b-chat
WATSONX_INSTRUCTION=[INST] <<sys>>\n ....(略)
WATSONX_PRE_INPUT=[User] 
WATSONX_POST_INPUT= \n[/User]\n

最後にアプリケーションを実行します:
$ node app

server starting on 8080 ...

"server starting on 8080 ..." と表示されていれば、アプリケーションの実行ができています。



【英会話アプリを実行する】
では実際に英会話アプリを実行してみましょう。Chrome ウェブブラウザ(WebAudio API を使うため、FireFox は NG)を起動し、"http://localhost:8080/" にアクセスしてみましょう:
2023112903


↑上のような画面が表示されると思います(途中でマイク許可のダイアログが表示されたら「許可」してください)。この画面を使って英会話を練習します:
2023112904


女性の口の部分がマイクのスイッチになっています(最初は青いボタンです)。ここをクリックするとマイクがオンになるので、マイクオンの状態で英語で話しかけてみてください:
2023113001


話し終わってある程度の無入力時間が経過すると、それまでに話した内容を WebAudio API が英語で解釈し、その解釈結果が画面に表示されます:
2023113004
(↑ "Hello. May I have your name, please?" と聞いてみました)


同時に解釈した結果(を英会話向けにプロンプトで加工したもの)を watsonx.ai が読み取り、英会話として続くような内容を返し、最後に WebSpeech API を使って英語で答えてくれます:
2023113002
(↑ちゃんと名前を教えてくれ、続けて私の名前を聞かれました。ちゃんと英会話として成立しています)


同様にして再度マイクオンにしてからマイクに英語で話しかけると英語で答えてくれる、という作業を繰り返すことができます:
2023113003

(↑「キムラ」が「キモラ」になってしまいましたが、これは私の英語発音が良すぎたせいで・・・ということにしておきます)


ぼっちでも英会話練習がはかどりますね。
この仕組みの内部的な挙動を少し説明しておきます。watsonx.ai ではいくつかの基盤モデルと呼ばれる大規模言語モデルが提供されているのですが、今回はその中からメタ社が提供している Llama2 という言語モデルを採用しています(後述のプロンプト含めていくつか試してみた上で、自然な英会話を成立させることができそうだ、と判断しました)。

また音声入力のテキスト化と、生成 AI による出力結果であるテキストの音声出力については Chrome ブラウザの WebAudio API を使っています。この部分についてはサーバーサイド型の仕組みを使うとタイムラグが大きくなってしまうため、ブラウザ内で実装できる方法を検討した結果です。とはいえ、聞き取り精度も、話す際の自然さも悪くないと感じています。一方でブラウザ毎の WebAudio API の実装に伴う制約などがあった場合にその制約を受けることにもなります(例えば FireFox では動かない、など)。

ちなみに Chrome WebAudio API 自体は日本語にも対応していて、日本語でも聞き取り&しゃべりどちらも高い精度でした。別の機会に使えそうでした。

そして肝心のプロンプトです。言葉で発した音声をテキスト化し、それをそのまま生成 AI へ・・・というわけではありません。実は上で .env ファイルを編集した時の4行目にこのような長い一行がありました:
WATSONX_API_KEY=(API キーの値)
WATSONX_PROJECT_ID=(プロジェクト ID の値)
WATSONX_MODEL_ID=meta-llama/llama-2-70b-chat
WATSONX_INSTRUCTION=[INST] <<sys>>\nYou are a friendly, respectful and honest friend. Always answer as friendly as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature. Also try to ask something for me, not try to help me.\n\nIf a question does not make any sense, or is not factually coherent, tell that you don't understand that well. If you don't know the answer to a question, please don't share false information.\n\nYou are an English speaker.\nIf you are asked a question in Japanese, please answer that you don't understand Japanese, and if you are asked a question in English, please answer in English.\n\nThe user is at beginner level of English, so please answer in easy and short English sentence as much as possible. Also you don't speak so much.\n\n<</SYS>>\n[/INST]\n
WATSONX_PRE_INPUT=[User] 
WATSONX_POST_INPUT= \n[/User]\n

詳しくはソースコード内を参照していただきたいのですが、実はこの4行目がプロンプトの指示部分になっていて、音声入力したテキスト部分に先立って入力されています。その内容を日本語で書くと、このような内容です:
あなたは優しく正直な友人です。可能な限りフレンドリーに回答してください。
でも相手を傷つけたり攻撃するような言葉は使わないでください。

・・(中略)・・

あなたは英語を話します。日本語で質問されても英語で「日本語はわからない」と答えてください。
英語で質問されたら簡単な英語で端的に答えてください。・・

このインストラクションに続いて、実際に喋った内容が英語で続けられたテキストが生成 AI に渡されています。このインストラクション部分がプロンプトに含まれていることで、(比較的初級の)英会話
アプリとして動くようになっています。

つまりこの .env の4行目部分を更にカスタマイズすることで英会話としての挙動を変えることもできると思っています(この部分が長くなる場合は現在 100 で固定している max_token 長も変更の必要があるかもしれません)。まあそのあたりに関しては(プロンプトエンジニアリングの楽しい部分でもあるので)ぜひ皆さんで試してみてください。 ・・といいつつ、実は私もこの辺りは同僚の後輩に手伝ってもらってたりするんですけど(笑)。


このブログエントリは Oracle Cloud Infrastructure アドベントカレンダー 2023 に参加しています。シリーズ2の 12/03 ぶんの記事です(↓の記事内容は 2023 年 12 月 02 日時点の内容で記載しています):
2023120101


このブログで紹介するテーマは「プライベート heroku っぽい PaaS 環境を無料で作る」というものです。個人プログラマにとって無料で使える環境というのはありがたいもので、Oracle Cloud Infrastructure(OCI) の always free tier は本当に貴重な環境であります。この環境に加えて、
sslip.io
dokku
という2つの無料サービス+無料アプリを使って、自分専用の heroku っぽい PaaS 環境を構築する(ついでに GitHub Actions も併用して CI/CD 環境も用意する)、というものです。


【heroku っぽい環境】
そもそも「heroku っぽい PaaS 環境」の「heroku っぽい」 環境というのがどんな環境なのか、heroku をご存じな人であれば heroku そのものをイメージしていただくのがいいと思うのですが、ご存じない人向けに私の理解で簡単に紹介しておきます。

heroku は 2007 年に創業したクラウドサービス事業名であり、その運営会社名です。Java, JavaScript, Ruby, PHP といった各種プログラミング言語によって開発されたウェブアプリケーションを仮想コンテナ上で実行するプラットフォームを提供しています。ウェブアプリケーションに加えて各種データベースや管理・監視系サービスも併せて利用することができます。最大の特徴は PaaS(Platform as a Services) を体現するシンプルさで、手元にアプリケーションのソースコードがあれば(あるいは GitHub などのソースコードリポジトリがあれば)コマンド1つでクラウド上にアプリケーションをデプロイ/更新/スケールすることができます。"*.herokuapp.com" というアプリ公開用ドメインが提供されていて、早い者勝ちでこのドメインを使ったホスト名によるアプリケーション公開ができます(カスタムドメインを利用することも可能です)。2010 年には買収に伴う形でセールスフォース・ドットコム傘下となり、セールスフォース連携機能も強化されました。

heroku は 2022 年 8 月までは無料枠を提供していましたが、現在は無料で利用することはできません。この記事で紹介する内容は、この heroku 環境を模した(というか、ほぼそのものの)「heroku っぽい」環境を自分専用に構築する、という内容です。


【sslip.io】
自分専用の heroku っぽい環境を作る、実はこれ自体は後述の dokku を使うことでさほど難しくなく構築することができます。問題は「無料で」の部分です。肝心のサーバーは OCI の always free tier などを使うことでなんとかなりそうなものですが、選択肢が少ないのが「アプリ公開用のドメインをどうするか?」でした。

この PaaS 環境はアプリケーションを1つだけ公開するわけではなく、リソースの許す限り(今回の例だと OCI の always free tier 環境の限界まで)使うことができます。つまり "app1.domain.net" と "app2.domain.net" と "app3.domain.net" と・・・のように複数のアプリケーションをこの環境で公開することができるのです。そこでこの "domain.net" に相当するドメインが問題になります。無料のホスト名を入手する DNS サービスなどもありますが、このような heroku っぽいプラットフォームを作ろうとすると、単に1つや2つのホスト名が使える DNS サービスでは不十分で、ワイルドカード DNS("*.domain.net" というワイルドカード付きホスト名に対応した DNS サービス)を、それも無料のものを使う必要があります。

そこで今回使うのが sslip.io です。ここは無料のワイルドカード DNS に対応した無料サービスなのですが、特筆すべきはその便利さです。事前のユーザー登録や設定なども不要(!)で、例えば
 XX.XX.XX.XX
というパブリック IPv4 アドレスであれば、
 XX.XX.XX.XX.sslip.io
という名前でアクセスできます。しかもワイルドカード DNS に対応しているので、
 *.XX.XX.XX.XX.sslip.io
というワイルドカードホスト名を(無料で)使うことができます。まさに今回のテーマのための神サービス! この sslip.io を使って PaaS 上にデプロイした各アプリケーションにホスト名でアクセスすることができるようになります。


【dokku】
そして dokku です。オープンソースの PaaS 環境は数あれど、構築や運用がここまで簡単なものは他にないと思っています(その代わり1サーバーで全環境を構築することになります。複数サーバーを束ねて PaaS 環境を作るのではなく、1サーバーだけのシンプルな構成で構築します)。

dokku 自体が「プライベート版 heroku」という代名詞で呼ばれることもあり、heroku と非常に似たインターフェースを持っています。dokku 本体が docker を内蔵しており、デプロイした各アプリケーションやデータベースは全て docker コンテナとして稼働します(docker コマンドを使って構築・運用するわけではありませんが、docker コマンドでコンテナの様子を確認したりもできます)。

また多くのプラグインも公開されており、例えば今回も使う Let's Encrypt プラグインを使うことでウェブアプリケーションを SSL 証明書に対応させることも(https アクセスに対応させることも)可能です。


これらのサービスやソフトウェアを組み合わせることで heroku っぽい PaaS 環境を無料で構築していきます。


【前提準備】
以下の構築作業に入る上での事前準備として、以下の準備までは完了しているものとします:

・OCI アカウント取得
Ubuntu ベースの always free tier インスタンスの作成
・同インスタンスへの SSH ログイン
・同インスタンスへの TCP 80 番ポート、および TCP 443 番ポートの解放

重要な点として、always free tier インスタンスは CentOS ベースではなく Ubuntu ベースのもの(デフォルトユーザー名が opc ではなく ubuntu のほう)にする必要があります。CentOS の場合、dokku の標準インストーラーではインストールすることができません。なんか色々工夫することで dokku インストール自体はできるようになるかもしれませんが、インストール後の運用手順とかにも影響ありそうなので、今回の記事では対象外とします。

また今回の作業の最終結果として構築する PaaS 環境ではデプロイされたウェブアプリケーションに 80 番(http)および 443 番(https)ポートで接続することになります。したがって Ubuntu 側もこれらのポートにアクセスされる想定でファイアウォールを準備しておく必要があるのでした(デフォルトでは OCI のサブネットと Ubuntu の ufw の両方が閉じられています)。この辺りはこちらの記事が参考になると思います(私も参考にさせていただきました):
Oracle Cloud(OCI)でポートを開放する方法


前提準備がそろった所で dokku を使ったプライベート PaaS 環境の構築を行います。


【dokku 環境構築作業手順】
SSH で対象の Ubuntu サーバーにログインします。以下、しばらく CLI での作業になります。とりあえずお約束のリポジトリ更新コマンドを実行しておきます:
$ sudo apt update -y && sudo apt upgrade -y

dokku はインストール用のスクリプトが提供されているので最初にダウンロードします:
$ wget -NP . https://dokku.com/bootstrap.sh

sudo を付けてインストールスクリプトを実行します。その際にバージョン番号(2023/12/02 時点での最新バージョンは v0.32.3)を指定します:
$ sudo DOKKU_TAG=v0.32.3 bash bootstrap.sh

このインストール作業は結構時間がかかります(docker のインストールや初期状態で必要なイメージのダウンロードも含みます)。作業が完了してプロンプトが戻るまでしばらく待ちます(ネットワーク次第ですが 10 分前後?)。

インストールが完了したら dokku にドメインを設定します。今回使う always free tier の Ubuntu サーバーのパブリック IP アドレスが XX.XX.XX.XX であったとして、以下のコマンドを実行します(XX.XX.XX.XX 部分を実際の IP アドレスに替えて実行してください):
$ dokku domains:set-global XX.XX.XX.XX.sslip.io

次に秘密鍵と公開鍵を dokku に登録します。dokku は dokku 自体が git リポジトリを内包していて、この内包 git リポジトリを使ってアプリケーションをデプロイするのですが、その際に利用する鍵ペアを登録する必要があるのでした。お持ちの鍵ペア(秘密鍵ファイルと公開鍵ファイル)があればそれを使うこともできるのですが、後述する CI/CD 機能のため「パスフレーズのない鍵ファイル」を用意することをお勧めします。ここではパスフレーズのない鍵ファイルを作成する手順を紹介します。

以下のコマンドを実行します。実行直後に作成する鍵ファイル名が表示されるので(特に変更の必要がなければ)そのまま Enter キーを押してください。次にパスフレーズの入力を求められますが、今回は何も指定せずにそのまま Enter キーを押してください(再度同じパスフレーズの入力を求められた時もそのまま Enter):
$ ssh-keygen -t rsa

Generating public/private rsa key pair.
Enter file in which to save the key (/home/ubuntu/.ssh/id_rsa): ←[Enter]キーを押す
Enter passphrase (empty for no passphrase): ←パスフレーズを入力せず [Enter] キーを押す
Enter same passphrase again: ←もう一度そのまま [Enter] キーを押す
Your identification has been saved in /home/ubuntu/.ssh/id_rsa.
Your public key has been saved in /home/ubuntu/.ssh/id_rsa.pub.

この操作が完了すると /home/ubuntu/.ssh/ フォルダ内に秘密鍵(id_rsa)と公開鍵(id_rsa.pub)の2つが作成されます。念のためパーミッションを 400 に変更しておきます(デフォルトでそうなっていると思いますが、念のため):
$ chmod 400 ~/.ssh/id_rsa*

これで dokku で使う秘密鍵ファイルと公開鍵ファイルが用意できました(既に所有していた鍵ファイルを使う場合はここから)。

まず以下のコマンドを実行して秘密鍵を登録します:
$ eval "$(ssh-agent)"

$ ssh-add -k ~/.ssh/id_rsa

続いて以下のコマンドを実行して公開鍵を dokku に登録します:
$ cat ~/.ssh/id_rsa.pub | sudo dokku ssh-keys:add admin

dokku 自体の環境構築はこれでほぼ完了していますが、ついでに前述の Let's Encrypt プラグインも導入しておきます。以下のコマンドを実行してください(2つ目のコマンドは Let's Encrypt で SSL 鍵を作る際に必要なメールアドレスの指定です):
$ sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git

$ dokku config:set --global DOKKU_LETSENCRYPT_EMAIL=(自分のメールアドレス)

ここまでの作業で dokku のインストール/セットアップは完了しました。


【アプリケーションをデプロイ】
dokku の動作確認の意味も含めて、この状態で一度ウェブアプリケーションをデプロイしてみましょう。自分で使ってみたいアプリケーションがあればそれを使っていただいてもいい※のですが、特にない方は私のアプリを使ってみてください(一応 Node.js で作られていますが、単にメッセージを表示するだけの本当にシンプルなウェブアプリケーションです):
https://github.com/dotnsf/simpleweb


※自作のものなど、他のアプリケーションを使っても構いませんが、この段階ではデータベースなどを併用しないものを用意してください。


まずはアプリケーションのソースコードを入手します。改めて Ubuntu の CLI で以下を実行してソースコードを git clone してください(自分のソースコードを使う場合は自分のコードの URL を指定してください):
$ cd

$ git clone https://github.com/dotnsf/simpleweb

$ cd simpleweb

このローカルリポジトリに dokku 用のオリジンを追加します(localhost の git を使って dokku という名前で simpleweb のオリジンを作成しています):
$ git remote add dokku dokku@localhost:simpleweb

このオリジンからデプロイする先のアプリケーション(simpleweb)を dokku 環境にあらかじめ追加しておきます:
$ dokku apps:create simpleweb

では実際にアプリケーションをデプロイします。git コマンドで作成したオリジンに push することで dokku 内へのデプロイが実行される仕組みが提供されているので、デプロイ作業は単に目的のオリジン(dokku)へ main ブランチを git push するだけです(秘密鍵にパスフレーズが指定されている場合はここで入力を促されるので正しいパスフレーズを入力してください):
$ git push dokku main

git push 実行後にデプロイが開始されます。ここではソースコードの内容からプラットフォーム環境(この simpleweb アプリの場合は Node.js 環境)を特定し、このプラットフォーム向けに docker コンテナイメージが作成され、そのイメージが dokku 内の docker コンテナとしてデプロイされることで完了します(というわけで、他のコマンドと比較して長い時間(約1~2分)がかかります):
2023120102


無事にデプロイが完了したら、最後にポート番号のプロクシ設定をします。この simpleweb アプリケーションは 8080 番ポートで稼働するよう作られているので、80 番ポートから 8080 番ポートに転送してアクセスできるよう設定します:
$ dokku proxy:ports-add simpleweb http:80:8080

2023120103


これで simpleweb アプリケーションが 80 番ポートで公開されているはずです。ウェブブラウザを起動して、
  http://simpleweb.XX.XX.XX.XX.sslip.io/
にアクセス("XX.XX.XX.XX" は Ubuntu サーバーの IP アドレス)し、以下のような画面になることを確認してください。このような画面が表示されれば成功です。dokku が正しくインストールされ、アプリケーションもデプロイできて、sslip.io を使ったホスト名での(HTTP による)アプリケーションアクセスができることが確認できました:
2023120104


ついでに HTTPS アクセスを有効にしましょう。コマンドラインで以下のコマンドを実行します:
$ dokku letsencrypt:set simpleweb email (自分のメールアドレス)

$ dokku letsencrypt:enable simpleweb

2023120105


ここまで正しく実行した上でウェブブラウザをリロードすると(TSHS が有効になっているので)自動的に HTTP から HTTPS に切り替わり、https アクセスで同じ画面が表示できるはずです(もちろん直接 https://simpleweb.XX.XX.XX.XX.sslip.io/ にアクセスしても同じ結果になります):
2023120106


【CI/CD】
最後に Github Actions を使った CI/CD 環境の構築手順についても紹介しておきます。GitHub でバージョン管理しているソースコードが更新されたタイミングで自動的に dokku 上のアプリケーションを最新ソースコードのアプリケーションに更新する、というものです。

まずは GitHub 上に管理対象のソースコードリポジトリを用意します。プライベートリポジトリでもパブリックリポジトリでもどちらでも構いません。以下では https://github.com/dotnsf/web-app-test というリポジトリを使った例を紹介しますが、実際に試す場合は実際の(自分が開発者としてアクセスできる)リポジトリを使ってください:
https://github.com/dotnsf/web-app-test


そしてそのリポジトリを使って上述のアプリケーションデプロイ手順を実行しておきます。つまり一度手動でオリジンを追加(git add remote origin)して、アプリケーションを作成(dokku apps:create)して、デプロイ(git push dokku main)して、HTTPS アクセスを有効(dokku letsencrypt:enable)にしておき、アプリケーションに HTTPS アクセスができる状態を作っておきます:
2023120105


この状態から CI/CD 環境を構築していきます。まずは GitHub Actions を定義するのですが、手始めに秘密鍵を登録します。GitHub の該当リポジトリで "Settings" メニューを選択します:
2023120107


"Settings" 画面の左メニューから "Secrets and variables" - "Actions" を選択します:
2023120108


秘密鍵を登録したいので "Secrets and variables" 画面内の "New repository secret" ボタンをクリックします:
2023120101


秘密鍵を登録する画面が表示されます。NAME 欄には SSH_PRIVATE_KEY と入力します。また value 欄には上述の作業中に作成して使った秘密鍵ファイル(id_rsa)のテキスト内容を "-----BEGIN RSA PRIVATE KEY-----" から "-----END RSA PRIVATE KEY-----" まで)をそのままコピー&ペーストして入力し、最後に "Add secret" ボタンをクリックします:
2023120102


以下のように表示されれば、GitHub リポジトリへの秘密鍵の登録は完了です:
2023120103


次にリポジトリ内にプッシュ時に実行される GitHub Actions を定義します。そのためにソースコード内に .github/workflows/ というフォルダを作成し、その中に deploy.yml(つまり .github/workflows/deploy.yml ファイル)を以下の内容で作成します:
---
name: 'deploy'

# yamllint disable-line rule:truthy
on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Cloning repo
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Push to dokku
        uses: dokku/github-action@master
        with:
          # specify the `main` branch as the remote branch to push to
          branch: 'main'
          git_remote_url: 'ssh://dokku@XX.XX.XX.XX:22/web-app-test'
          ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}

青字XX.XX.XX.XX は Ubuntu サーバーのパブリック IP アドレスで、赤字web-app-test 部分は(dokku apps:create した時に指定した)アプリケーション名で、それぞれ置き換えて作成してください。またソースコードの書き換えが視覚的にもわかるよう、アプリケーション側にも何らかの変更を加えておいてください。

これらのファイルをリポジトリに追加して更新します:
$ git add .

$ git commit -m 'dokku CI/CD with Github Actions'

$ git push

最後の "git push" が完了すると上記で作成した Github Actions が起動し、定義内容に従って自動的にビルド/デプロイが開始されます。デプロイの様子は Github リポジトリの "Actions" メニューから確認することが可能です:
2023120104


デプロイ完了後にアプリケーションにアクセスすると、最新のソースコードでデプロイされたアプリケーションが表示されるはずです(元のアプリで表示されていたメッセージの最後に "!" を追加する変更を加えていたのですが、正しく反映されていました):
2023120106


これで CI/CD も実現できたことになります。ソースコードの main ブランチが更新されてコミット/プッシュされるたびにアプリケーションも自動更新されてデプロイしなおされる、という便利な機能も使うことができそうでした。


【おまけ】
自分が dokku を使っていて気付いたことなのですが、どうもデプロイのたびに変なゴミというか、使われることのないファイルが残ってしまうらしく、特に CI/CD を使って何度もデプロイしていると、どんどんディスクの残り容量が減っていってしまうようでした。これを回避するには定期的に以下のコマンドを実行する必要があります:
$ docker system prune -f && docker system prune --all -f

面倒でなければ手入力でもいいと思いますし、面倒な場合は crontab にでも登録してアクセス数が比較的少なそうな時間帯に実行するのがいいと思っています。


【まとめ】
以上、無料のサービスだけを組み合わせる形で、CI/CD 含めたプライベート heroku (っぽい)環境を作ることができました。理論上はこの環境にいくつでも複数のアプリケーションを登録して、別々に運用することができる環境なのですが、さすがに always free tier の環境(メモリ 1GB ディスク 30GB)で運用することを考えると、5~6アプリが限界ではないかと思っています。もちろんこのリソーススペック上の制約は有償版を使うことで回避できます。また有償版の OCI なら DNS も使えるので(sslip.io を使う形ではなく)カスタムドメインを利用して環境構築することもできるようになります。 とはいえ、「無料で構築できる PaaS 環境」という素敵な響きにも替え難い魅力がありますよね。いずれはリッチなスペックの VM +カスタムドメインで運用するとしても、初めのうちはこの無料環境でプライベート PaaS を楽しむのも悪くないと思っています。なお(無料というわけにはいきませんが)カスタムドメインやその DNS のセットアップまで含める形で dokku 環境を作ったこともあります(OCI は全く使ってませんけど・・)。その時の内容に興味ある方は私の dokku 関連の過去の記事を参照ください。

dokku に関して、とりあえず環境構築と試験的な運用までが可能な一連の手続きについてはこのブログエントリ内で紹介していますが、実際にはこの中で紹介していない色々な機能があります(スケール対応とか、データベース系のプラグインを併用するとか、・・)。dokku はオンラインヘルプも充実しているので、詳しくはドキュメントを参照してください。

最後に注意点を。今回紹介した方法で dokku 環境を構築してアプリケーションを公開すると、事実上サーバーの IP アドレスを公開する形になります(sslip.io を使っているので、アプリケーションのホスト名の中に IP アドレスが含まれることになるからです)。サーバーの IP アドレスがバレても困らないように SSH などはしっかりセキュアに対策しておくといった対策はしっかりしておきましょう。

私自身は実際に dokku で(カスタムドメインを使って)自作サービスをいくつか運用しています。2年以上使っていますが、とても便利です。この記事が私と同じような技術クラスタの人のお役に立てれば何よりもうれしいです。


これらの記事の続きです:
nsf2dxl2web の挑戦(1)
nsf2dxl2web の挑戦(2)
nsf2dxl2web の挑戦(3)
nsf2dxl2web の挑戦(4)
nsf2dxl2web の挑戦(5)


ノーツデータベースのウェブ化ツールである nsf2dxl2web を紹介しています。5回目となる今回は検索編と位置付けています。 nsf2dxl2web そのものの機能紹介ではないのですが、nsf2dxl2web でウェブ化したデータベース内のビュー/フォルダや各文書を検索する仕組みをオープンソース全文検索サーバーである Fess を使って構築する手順を紹介します。なお一般的な Fess の使い方となるクイックスタートはこちらを参照ください:
https://fess.codelibs.org/ja/quick-start.html


まず、今回のために nsf2dxl2web を使って2つのノーツデータベースをウェブ化したサイトを以下に公開しました:
https://dotnsf.github.io/nsf2dxl2web_githubpages/

2023112602


アクセスすると上図のように2つのノーツデータベース(「NSF2DXL2WEB サンプル」と「サンプルDB」)をウェブ化したサイトが表示されます。この2つのノーツデータベースは私が nsf2dxl2web の動作テスト用に UI 含めて作ったものです(「サンプル DB」はリンク先のテスト目的で作ったものなので1文書だけのシンプルなものです。「NSF2DXL2WEB サンプル」にはサブフォームや添付ファイルを含めた様々なリッチテキスト要素が含まれています。前回説明しましたが、検索対象となることを想定してどちらも Standard UI のビュー/フォルダ形式にしています)。もともとノーツのワークスペース上に存在していた時の様子は以下なので、データベースアイコン含めて再現されていることが改めて確認できると思います:
2023112603


"NSF2DXL2WEB サンプル" と書かれたアイコンをクリックすると、選択したノーツデータベースを nsf2dxl2web を使ってウェブ化したページが表示されます:
2023112609


ではこのノーツデータベースをウェブ化した上記サイトを Fess で全文検索する手順を以下に紹介します。このサイトはパブリックに公開しているので、よかったら皆さんの Fess 構築作業(後述)の練習にも使ってみてください。


【Fess について】
FessJava で開発されたオープンソースの全文検索エンジンです。商用サポートのついたソフトウェアである N2SM Search のコミュニティ版、という位置づけです。

Fess は主に以下の機能が提供されています:
・ウェブのデータ収集と、収集したページの全文検索エンジン(今回紹介する機能)
・ファイルサーバーのデータ収集と、収集したファイルの全文検索
・データストア(CSV や RDB など)のデータ収集と、収集したデータの全文検索エンジン

Fess には Windows 版と Linux 版があり、Linux 版は rpm 形式や deb 形式でのパッケージ配布に加え、docker に対応したコンテナイメージも提供されています。本ブログエントリでは docker 版を使って構築手順を紹介します。


【Fess のインストール】
以下では docker 版の Fess をインストールする手順を紹介します。前提として docker (と docker-compose)がインストールされた x86_64 版の Linux 環境が必要になるため、自分の PC やクラウドサーバー上に用意しておいてください。

そして docker 導入済みの環境で以下のコマンドを実行して Fess を(正確には OpenSearch 版の Fess を)インストールします:
$ git clone https://github.com/codelibs/docker-fess.git

$ cd docker-fess/compose

$ sudo sysctl -w vm.max_map_count=262144

$ docker compose -f compose.yaml -f compose-opensearch2.yaml up -d
 または 
$ docker-compose -f compose.yaml -f compose-opensearch2.yaml up -d 

補足として、3つ目のコマンドを実行する理由はこの設定が推奨されていたためです。また4つ目のコマンドで実際に Fess を起動するのですが、Fess の検索機能で CORS の有効/無効を切り替えたい場合(Fess サーバーの外部から JavaScript で検索した場合などは有効にする必要があります)は4つ目のコマンドを実行する前に compose-opensearch2.yaml ファイルを編集して CORS コマンドの有効/無効を切り替えておく必要があります:
  :
services:
  es01:
    environment:
      - "api.cors.allow.origin=*"
      - "api.cors.allow.methods=GET, POST, OPTIONS, DELETE, PUT"
      - "api.cors.max.age=3600"
      - "api.cors.allow.headers=Origin, Content-Type, Accept, Authorization, X-Requested-With"
      - "api.cors.allow.credentials=true"
  :

最後のコマンドを実行すると Fess が起動します。初回のみイメージのダウンロードを伴うため時間がかかりますが、(イメージのダウンロードが済んだ)2回目以降はすぐに起動できるはずです。

Fess は 8080 番ポートで起動します。コマンド完了後に docker ホストの 8080 番ポートにアクセスし、以下のような画面が表示されればインストールが成功しています:
2023112604


デフォルトでは
 ユーザーID: admin
 パスワード: admin
のアカウントが作成されています。「ログイン」と書かれたリンクからログインします:
2023112605


初回ログイン時のみパスワードの更新が求められるので、新しいパスワードに更新します。以後、この新しいパスワードでログインしているものとして説明を続けます:
2023112606



【Fess で nsf2dxl2web サイトの全文検索インデックスを作る手順】
では Fess に(nsf2dxl2web で作ったウェブの)新しい検索インデックスを登録します。ログイン名の所をクリックし、「管理」を選択します:
2023112601


下のような管理画面が表示されます。画面左上にハンバーガーメニューがあることを確認します:
2023112602


ハンバーガーメニューから 「クローラー」-「ウェブ」 を選択します:
2023112603


ウェブのクローラー設定の一覧画面が表示されます(最初の状態では何も登録されていません)。ここに新しいウェブを追加するので、画面右上の「新規作成」をクリックします:
2023112604


ウェブクロールの新規設定画面が表示されます:
2023112605


ここに以下の内容を入力していきます(書かれていない項目はデフォルト値のまま):
・名前:(任意の設定の名称 例えば "NSF2DXL2WEB サンプル")
・URL:検索先ページの URL、上記例であれば "https://dotnsf.github.io/nsf2dxl2web_githubpages/49258A000002200B/index.html"
・クロール対象から除外する URL:(デフォルトから "|xml" を削除したもの)
・深さ:3

2023112606


なお、除外する URL から "|xml" を除いた理由ですが、デフォルト設定のままだと "xml" で終わる URL はクロールされないことになります。nsf2dxl2web でウェブ化したサイトの場合、各文書の URL は ".xml" で終わるので、このデフォルト設定のままだと文書データが検索インデックスに含まれなくなってしまいます。この状況を回避する目的でこの設定にしています。

また「深さ=3」を指定する意味ですが、ここでの「深さ」とは「ページ内のリンクを何段階まで辿るか」を意味しています。nsf2dxl2web でウェブ化した場合、対象 URL は index.html を指しているので、
 深さ=0:index.html のみ
 深さ=1:views.html (ビュー/フォルダ一覧)まで
 深さ=2:各ビュー/フォルダの HTML まで
 深さ=3:各文書まで(Standard UI の場合のみ)
を対象にクロールすることになります。というわけで、各文書のデータまで検索できるよう、ここでは「3」を指定しています。

設定できたら最後に「作成」ボタンをクリックします:
2023112607


ウェブのクローラー設定の一覧画面に戻ります。いま作成した設定が追加されていることを確認してクリックします:
2023112608


自動で付与された ID などを含め、作成した内容が表示されます。この登録内容を使い、「ジョブ」という単位でクロールを実行することになります。そのジョブを新規に作成するため、最下部の「新しいジョブの作成」ボタンをクリックします:
2023112601


画面に色々表示されますが、すべてデフォルト値のままで構いません。最下部の「作成」ボタンをクリックします:
2023112602


すると「ジョブスケジューラ」画面に切り替わり、今作成したジョブが追加されていることが確認できます。この作成されたジョブを選択します:
2023112603


※なお、このジョブスケジューラの一覧画面はハンバーガーメニューから 「システム」-「スケジューラ」 を選択することでも表示することができます:
2023112602


作成したジョブの内容が表示されます。ジョブの実行タイミングやスケジュールを指定することができるのですが、nsf2dxl2web でウェブ化したノーツデータベースの場合は(このシリーズの初回に背景として説明したように、ノーツデータベース側の変更が加わることはほとんどない想定でウェブ化されていることが多いと思うので)1回実行しておけば2度目は不要ということがほとんどだと思っています。ほぼこのままで問題ないはずですが、必要に応じて実行スケジュールを変更してください。

このジョブを実行するには最下部の「今すぐ開始」をクリックします:
2023112604


ジョブの実行が開始された、という旨のメッセージが表示されます。これで指定した内容でのクローリングが開始されています:
2023112601


ジョブの実行状況を確認する場合はハンバーガーメニューから 「システム情報」-「ジョブログ」 を選択します:
2023112602


過去に実行したジョブのログが一覧で表示されています。「状態」列が「実行中」となっている場合はジョブが実行中であることを意味しています:
2023112603


この値が「OK」になっているとジョブの実行が完了、つまりクロールが完了して、検索可能な状態になっていることを意味しています。状態が「OK」になっていない場合は「OK」になるまで待ちます:
2023112601


これを必要なノーツデータベースのぶんだけ繰り返します。今回の場合は「NSF2DXL2WEB サンプル」に加えて「サンプル DB」も Standard UI で用意しているので検索可能です。というわけでこちらのデータベースについても同様にインデックスを作成して検索ジョブを実行し、両方が完了している状態を作っておきました:
2023112602


なお、このサンプルのサイトの場合は(普通に公開されているので)不要ですが、検索対象先にベーシック認証がかかっている場合はハンバーガーメニューから 「クローラー」-「ウェブ認証」 を選択して、ウェブ認証情報を追加することも可能です:
2023112601


では検索インデックスが準備できたので、実際に検索してみます。Fess のトップページに戻り、適当なキーワード(下図では「タブ」)を入力後に Enter キーで検索してみます:
2023112601


検索結果の一覧が表示されます。ちゃんと全文検索できているので、ビューも文書も検索結果に含まれています:
2023112602


試しに検索結果のある文書をクリックすると、その文書が正しく表示されます:
2023112603


この例では複数のノーツデータベースの検索インデックスを有効にしていたので、複数のデータベースにまたがる検索が可能です。複数のデータベース内に含まれる検索ワード(下の例では「テスト」)を指定して検索した検索結果は異なるデータベースのものが含まれるはずです。ノーツデータベースでいうところの「串刺し検索」が可能になっていることを確認できます:
2023112604



以上、ノーツデータベースの参照ウェブ化ツールである nsf2dxl2web の使い方を、カスタマイズや検索機能まで含めて5回にわたって紹介してきました。ノーツデータベースを参照目的に絞ってウェブ化するツールですが、XSL を採用することでノーツの設計思想に近い形のままウェブ化でき、そのためカスタマイズ時にもフォーム単位でのカスタマイズが可能になっていたり、nginx やクラウド/コンテナ化を併用することで抜群のパフォーマンスが期待できたり、参照用途には必須の全文検索まで含めて実現できることが確認できています。

ノーツをほぼ参照目的だけで使い続けているケースではこういうウェブ化もアリなんじゃないかと思いながら作ってみた nsf2dxl2web がどこかで誰かの役に立つ日が来ることを願っています。


(過去記事はこちらです)
nsf2dxl2web の挑戦(1)
nsf2dxl2web の挑戦(2)
nsf2dxl2web の挑戦(3)
nsf2dxl2web の挑戦(4)
nsf2dxl2web の挑戦(5)




このページのトップヘ