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

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

2022/06

自分で開発したウェブサービスの公開先として、最近は dokku で作った公開プライベート(=自分専用)クラウド環境が重宝しています。デプロイはソースコードを git clone(pull) して git push するだけ、という非常に簡略化されていることに加え、やはり「自分専用(=使いたいサブドメインを誰かに先に取られている心配がない)」という気持ちの余裕も以外と大きい気がしています。dokku の導入については過去の記事を参照してください:
dokku でプライベート PaaS 環境を構築する(1)
dokku でプライベート PaaS 環境を構築する(2)


今回は dokku のいわゆる CI/CD(Continuous Integration / Continuous Delivery) についてブログエントリを書きます。dokku が非常に便利であるが故に、ソースコードが更新されるたびに手作業で push するのは(大した手間ではないのですが)やはり面倒です。というわけで Git リポジトリへのプッシュをフックとする dokku のデプロイ自動化を実現する方法を調べてみました。特に今回紹介するのは GitHub Actions を使った方法で、ソースコードを GitHub リポジトリで管理している人であればちょっとした設定を追加するだけで CI が実現できます。なお、今回 CI/CD を設定する dokku サーバーは "yellowmix.net" というカスタムドメインを有効に設定してあって、"XXXXX.yellowmix.net" というホスト名でアプリケーションをデプロイできるように設定済みである、という前提で紹介します(手順3の中にこの設定に関わる部分があります)。


以下、その手順紹介です。


【手順1 パスフレーズなしの秘密鍵/公開鍵を作成・登録】
GitHub Actions 内でデプロイを自動実行するため、パスフレーズ無しで作られた秘密鍵が必要です。というわけで、パスフレーズ有りの秘密鍵を使っている場合は作成および登録の再実行が必要になります。

まずは秘密鍵と公開鍵を(再)作成します。dokku サーバーのシェルにログインして、以下のコマンドを実行します:
$ ssh-keygen

この後、秘密鍵を作成するパスを入力するよう促されます。デフォルトでは "~/.ssh/id_rsa" が指定されているはずです。変更する場合は正しいファイルパスを入力、デフォルト設定のままで問題なければそのまま Enter を入力します。

次にファイルのパスフレーズを入力するよう促されますが、今回はパスフレーズのない秘密鍵および公開鍵のペアを作りたいので、空のまま Enter を押して先に進めます。パスフレーズの指定が終わると指定されたパスで秘密鍵および公開鍵が作成されます。特に秘密鍵の内容は手順2でも必要になるので、 cat コマンドで一度中身を確認しておきます("-----BEGIN RSA PRIVATE KEY-----" で始まって "-----END RSA PRIVATE KEY-----" で終わる内容が表示されます):
$ cat ~/.ssh/id_rsa

秘密鍵が作れたら dokku に登録します。上述のセットアップを既に別の(パスフレーズ付きの)秘密鍵で実施済みで、別の秘密鍵が登録済みの場合は、以下のコマンドで一度削除しておきます:
$ sudo dokku ssh-keys:remove admin

改めて(再度)秘密鍵をファイルパスを指定して登録します:
$ cat ~/.ssh/id_rsa | sudo dokku ssh-keys:add admin

これで新しい(パスフレーズのない)秘密鍵が作成され、 dokku に登録されました。続いて GitHub のリポジトリ側で GitHub Actions 側の設定を行います。


【手順2 GitHub の対象アプリケーションに GitHub Actions 用の秘密鍵を設定】
GitHub リポジトリにコミットされたタイミングでデプロイを実行できるよう、対象アプリケーションの GitHub リポジトリに GitHub Actions を設定します。まずは GitHub の対象アプリケーションのページを開き、Settings を選択します(ユーザーの Settings ではなく、アプリケーションの Settings を開きます):
2022060901


CI ワークフローを定義する前に、ワークフロー内で使う秘密鍵の情報を先に登録しておきます。Settings ページの左ペインから Secrets - Actions を選択します:
2022060902


新規に秘密鍵を登録したいので、画面右上の "New repository secret" ボタンをクリックします:
2022060903


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


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



【手順3 対象アプリケーションに GitHub Actions を設定】
ここまでの準備ができていれば、後はコミット時(プッシュ時)の GitHub Actions を登録することで dokku への自動デプロイを行うことができるようになります。ソースコードに .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@yellowmix.net:22/hostname'
          ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}

このうち、青字部分(main と書かれた部分)は「どのブランチが更新された時に、どのブランチを対象にデプロイするか」を指定しています。今回は「 main ブランチ更新時に main ブランチの内容をデプロイ」したいので、このような設定にしています。master ブランチを使いたい場合はここを master に変更してください。 また赤字部分はデプロイ先のリポジトリ URL です。この例では "yellowmix.net" というドメインホストを使って https://hostname.yellowmix.net/ という URL でアプリケーションを公開するための設定にしています。この部分は皆さんの dokku 環境に合わせて変更する必要があるのでご注意ください。 加えて、最終行に手順2で設定した秘密鍵を使う指定がされています。秘密鍵は外部に漏れてはまずい情報なので、ハードコートや公開変数などは使わずに、このような形で登録済みのシークレット情報を参照するよう指定しています。

なお、この deploy.yml ファイルの中で dokku/github-action というリポジトリが指定されている箇所がありますが、これは dokku から提供されている GitHub Actions 連携用のコンテナイメージです。このコンテナイメージを使ってデプロイを行う、という作業内容になっています。同コンテナイメージのソースコードはこちらで提供されています:


ここまで用意できたら、対象のプロジェクトを GitHub にコミット&プッシュします(以下の例は main ブランチに直接コミットしている想定です):
$ git add .

$ git commit -m 'node v14'

$ git push origin main

プッシュが成功すると GitHub リポジトリで GitHub Actions が起動するはずです。起動の様子はリポジトリ内の "Actions" メニューから確認できます(画面は Actions が実行中の様子):
2022060906


実行が(成功か失敗かで)完了すると、その結果が表示されます(この例では緑マークが付いているので成功しています。失敗は赤):
2022060907


成功・失敗に関係なく、Actions 名部分をクリックすると、Actions の実行ログを見ることができます。特に失敗した場合などはこのログが原因を調べるヒントになっている可能性が高いので、失敗に終わった場合はまずログを参照することになると思います:
2022060908


GitHub Actions が成功していた場合、更新されたアプリケーションが指定 URL で稼働できているはずです。実際にアクセスして動作確認してみましょう:
2022060900


以上、GitHub Actions を使って dokku にアプリケーションを自動デプロイするための設定を紹介しました。dokku は専用のリポジトリに git push するだけでもデプロイが出来て便利ですが、一度 CI/CD の便利さを知ってしまうと「わざわざデプロイする手間が面倒」に感じられてしまうほど便利なので、今後 dokku を使って動かすアプリについては全てこの設定を有効にしてもいいかな、と感じています。


 

僕のことを個人的に知ってる人はご存知かもしれませんが、自分は今から25年ちょっと前に「ロータス株式会社」という企業でアプリケーション開発を担当していました。具体的に開発を担当していた製品の中には、昭和を代表する(苦笑)表計算ソフト 1-2-3(「ワンツースリー」)も含まれていました。ここ10年くらいはウェブアプリばかり作っていますが、まだ頭(や性格)がキレキレだった頃はパソコン向けのネイティブアプリも作っていた時代がありました。

自分が開発を直接担当していたのは Microsoft Windows 向けの 32bit 版でしたが、当時はまだ PC-DOS 版を無理やり Windows 向けに拡張した 16bit 版や、PC-DOS 版自体も 1995 年まではバージョンアップ対応をしていました。そんなこともあって当時の担当製品は今でも仮想マシン(VM)の形で残していたりします。今となっては貴重な PC-DOS/V(V7) の稼働環境だと思っています:
2022060400


※ついでにコメントしておくと、Lotus Notes バージョン1などの OS/2 資産も VM 化して残しています。自分が作った製品への愛は高めだと自認しています。


この PCDOS VM の中に懐かしいツール類と一緒に Lotus 1-2-3 R2.5J がインストールされています(赤く囲っているのが当時悪名高かったライセンスファイルです):
2022060401


今回、この環境を持ち出したのは理由があります。先日、スラドにこのようなタレコミがあったのを発見しました:
Lotus 1-2-3、Linux に移植される

(自分も 1-2-3 愛は大きい方だと思ってますが)自分も勝てなさそうな 1-2-3 愛を持っていそうな Tavis 氏がインターネット・アーカイブ転がっていた保存されていた SysV 版の 1-2-3 for UNIX を使って Linux 版(elf 版)を作ることに成功した、というものでした。未だに 1-2-3 への愛を持っているのは自分くらいかと思っていたのですが、世界は広いです。 Tavis さん、Good-job! d(o^


(横道)上記画像でファイラーと呼ばれていた FD を使ったスクリーンショットを掲載していますが、実は FD は Linux に移植済みだったりします。興味ある方はこのコマンド(Ubuntu 向け)を試してみてください:
$ sudo apt install fdclone

$ fd
2022060411



(横道おわり)で、そんな記事に触発されたというわけではないのですが、自分も久しぶりに上記 PC-DOS 版 1-2-3 環境を使ってせっかくなので何か試してみようと思いました。実は以前から気になっていたことがあって、それを検証する機会とさせていただきました。

その気になっていたことというのは、みなさんも一度は耳にしたことがあると思うのですが「ソフトウェア製品に実装された機能の多くは使われない」というものです。製品ベンダーとしては利用者からの要望を形にして様々な機能を実装しているのですが、ほとんどのケースでその多くは使われることがない、というものです。そういう自分も、例えばプログラミングエディタに VSCode を使っていますが、自分に必要な機能は当然のように知ってますが、正直知らない機能が多くあります(良く言えば「困ったときにすぐに調べることができるので、使わない間は知らなくてもよい」わけです)。現在、表計算ソフトといえば「エクセル」ですが、このエクセルも(特に最近のバージョンは)非常に多くの機能が付与されていて、噂では方眼紙としても使うことができるその全てを熟知している人って存在するのだろうか??とすら思ってしまうほどです。

エクセルを「最低限」(この定義が難しい)使えるようになるにはどんなことを知っていればいいんだろう?? という需要もあるようで、YouTube を見ていてもエクセルの基本機能を紹介するものが多く見つかります。

・・・と、そこで閃きました。現在のエクセルで「基本」とされている機能って、Lotus 1-2-3 の頃にはどの程度存在していたのだろうか? という命題です。特に他意はないのですが、この手のエクセル基本機能を調べようとググった結果の上位にあったもので、YouTube でエクセルの基本機能等を紹介しているシリーズを見つけ、そのシリーズ中の「【Excel基本】初心者が最初に覚える関数12選」の中で紹介されている12個の関数を基本機能とみなして、これらの関数が DOS 版 Lotus 1-2-3 の中の関数としてどの程度カバーされていたのか、を検証してみることにしました(勝手に使わせていただきました m(__)m):
https://www.youtube.com/watch?v=cfGKFhSE6uA



なお、この動画で紹介されていた関数12選は以下のような内容でした:
1 SUM関数
2 AVERAGE関数
3 COUNT関数
4 COUNTA関数
5 IF関数
6 COUNTIF関数
7 COUNTIFS関数
8 SUMIF関数
9 SUMIFS関数
10 VLOOKUP関数
11 IFERROR関数
12 XLOOKUP関数


比較対象は Windows 版の Lotus 1-2-3 と DOS 版の Lotus 1-2-3 を・・・と考えていたのですが、今回の12個の関数について検証した結果としてはこの2つに差異はありませんでした(Windows 版に存在している関数は DOS 版にも存在していて、Windows 版に存在していない関数は DOS 版にもありませんでした)。ということもあって、以下では DOS 版での検証結果のみを紹介します。


検証方法としては、上記の YouTube 動画で「エクセルの基本関数」として紹介されている機能と互換性のある関数が DOS 版の Lotus 1-2-3 でも存在しているかどうかを確認する、という形としました。なお動画内で対象としているエクセルは最新の 2021 で、私が検証に使ったのは DOS 版 Lotus 1-2-3 R2.5J というバージョンです。このバージョン自体は 1995 年にリリースされた、DOS の日本語対応版としては最終バージョンですが、基本的な設計は R2 から変わっておらず、1986 年の設計を引き継いでいます(つまり昭和時代のソフトウェアです)。


【検証準備】
(VM 内の)PC-DOS に Lotus 1-2-3 R2.5J をインストールし、適当な表を作った上で関数の検証を行います:
2022060402


【検証1 SUM関数】
指定エリア内の数値を合計する、という(本当の意味で基本的な)関数です。これは Lotus 1-2-3 R2.5J では @SUM 関数として実装されていました(下図の赤枠部分参照)。なお Lotus 1-2-3 ではマクロ関数は全て @XXX という形式になっていて、「アット関数」と呼ばれています:
2022060403


【検証2 AVERAGE関数】
指定エリア内の数値の平均値を求める関数です。これも Lotus 1-2-3 R2.5J では @AVG 関数として実装されていました(下図の赤枠部分参照):
2022060404


【検証3 COUNT関数】
指定エリア内に数値のセルがいくつ存在するかをカウントする関数です。これも Lotus 1-2-3 R2.5J では @PURECOUNT 関数として実装されていました(下図の赤枠部分参照)。1-2-3 にも @COUNT 関数は存在しているのですが少し用途が違っていました:
2022060405



【検証4 COUNTA関数】
指定エリア内に空白ではないセルがいくつ存在するかをカウントする関数です。これも Lotus 1-2-3 R2.5J では @COUNT 関数として実装されていました(下図の赤枠部分参照)。1-2-3 の @COUNT 関数はエクセルの COUNTA 関数で、エクセルの COUNT 関数は 1-2-3 の @PURECOUNT 関数として存在しているのですが少し用途が違っていました:
2022060406


【検証5 IF関数】
条件分岐処理をする関数です。これは Lotus 1-2-3 R2.5J でも @IF 関数として実装されていました(下図の赤枠部分参照):
2022060407


【検証6 COUNTIF関数】
指定エリア内に条件を満たすセルの数かいくつ存在するかをカウントする関数です。これは Lotus 1-2-3 R2.5J には互換関数が存在していません


【検証7 COUNTIFS関数】
指定エリア内に複数の条件を満たすセルの数かいくつ存在するかをカウントする関数です。これは Lotus 1-2-3 R2.5J には互換関数が存在していません


【検証8 SUMIF関数】
指定エリア内で条件を満たすセルだけを対象とした SUM 関数です。これは Lotus 1-2-3 R2.5J には互換関数が存在していません


【検証9 SUMIFS関数】
指定エリア内で複数の条件を満たすセルだけを対象とした SUM 関数です。これは Lotus 1-2-3 R2.5J には互換関数が存在していません


【検証10 VLOOKUP 関数】
指定エリア内で特定の値をもつ列を見つけた場合に、指定された別の列の値を求める関数です。これは Lotus 1-2-3 R2.5J でも @VLOOKUP 関数として実装されていました(下図の赤枠部分参照):
2022060409



【検証11 IFERROR 関数】
エラーが発生した場合の処理を指定する関数です。これは Lotus 1-2-3 R2.5J には互換関数はありませんでしたが @IF 関数と @ISERR 関数を組み合わせる形で同様の機能を実装できました(下図の赤枠部分参照):
2022060408


【検証12 XLOOKUP関数】
VLOOKUP 関数の上位互換となる関数です。エラー発生時の処理も関数内に含めることができます。これはエクセル 2021 で登場した新関数でもあり、Lotus 1-2-3 R2.5J には互換関数が存在していません。


【検証結果まとめ】
というわけで、検証結果をまとめるとこのようになりました:
#エクセルの関数1-2-3 の互換関数
1SUM@SUM
2AVERAGE@AVG
3COUNT@PURECOUNT
4COUNTA@COUNT
5IF@IF
6COUNTIF -
7COUNTIFS -
8SUMIF -
9SUMIFS -
10VLOOKUP@VLOOKUP
11IFERROR@IF と @ISERR
12XLOOKUP -


エクセルの基本関数12個のうち、6個は互換関数が(くどいようですが昭和時代の)1-2-3 にも存在していて、1つは他の関数の組み合わせでなんとかなる、という感じでした。「現在の基本」のうちの半分くらいは当時から使えていた、ということになります。1-2-3 は「条件付きで○○をする」という関数が全般的に弱い感じがしますが、逆に言うとそのような需要があってエクセルで実装されていったのだと思われます。特筆したいのは VLOOKUP 関数で、これは今でも中級者以上が比較的使う機会の多い関数だと思っていますが、その関数が 1-2-3 でも実装されていたんですね。オーパーツのような話で、私も少し驚きました。



なお、今回のブログエントリを書く際の参考文書はこちらでした。今となっては貴重品な、ナツメ社のハンドブックシリーズです:
IMG_20220530_080350_947



前回dokku を使ったプライベート PaaS 環境の構築、およびシンプルなアプリケーションのデプロイ手順を紹介しました。 今回はより実践的なアプリケーションとして PostgreSQL データベースを併用するアプリケーションのデプロイ手順を紹介します(といっても、実は heroku を CLI で操作する時の手順とあまり変わりません・・)。

なお今回紹介する内容は、前回のセットアップ時に "withcorona.world" という独自ドメインを設定している想定で紹介しています。異なるドメインで設定されている場合は自分で設定したドメインに適宜読み替えてください。


【dokku 内で PostgreSQL データベースを動かす】
まず dokku 環境内にデータベースサーバーを用意します。今回は PostgreSQL を使うケースを想定して以下で紹介します。まずは dokku サーバーにログインしておきます。

dokku ではいくつかのサービスが「プラグイン」という形で連携できるよう用意されています。PostgreSQL もその1つです。というわけで、まずは dokku にログインして PostgreSQL プラグインをインストールします:
# dokku plugin:install https://github.com/dokku/dokku-postgres.git

インストールが完了すると PostgreSQL データベースをインスタンス化することができるようになります。例えば "mydb" という名前を付けて1インスタンス作るには以下のように入力します:
# dokku postgres:create mydb

作成後に作ったデータベースインスタンスの情報を確認する場合は以下のように入力します:
# dokku postgres:info mydb

=====> mydb postgres service information
       Config dir:          /var/lib/dokku/services/postgres/mydb/data
       Config options:
       Data dir:            /var/lib/dokku/services/postgres/mydb/data
       Dsn:                 postgres://postgres:XXXXXXXX@dokku-postgres-mydb:5432/mydb
       Exposed ports:       -
       Id:                  a017a6896694987cb0e729b4ec1042f831eecd0d8f726d52eeea435ecd9fcf4e
       Internal ip:         172.17.0.3
       Links:               -
       Service root:        /var/lib/dokku/services/postgres/mydb
       Status:              running
       Version:             postgres:14.2

この確認結果の Dsn 値として紹介されている "postgres://" で始まる文字列がいわゆる接続文字列になっていて、(後述する)環境変数の値になります。 このインスタンスのシェルに入って PostgreSQL の CLI を使ってテーブルを1つ定義しておきたいので、以下のように実行してください(データベースに接続する時は接続文字列の "dokku-postgres-mydb" 部分を "localhost" に変えて実行してください):
# dokku postgres:enter mydb (シェルにログイン)

/# psql "postgres://postgres:XXXXXXXX@localhost:5432/mydb" (psql でデータベースに接続)

mydb=# create table if not exists items ( id varchar(50) not null primary key, name varchar(50) default '', price int default 0, created bigint default 0, updated bigint default 0 ); (SQL で items テーブル作成)

mydb=# \q (データベースから切断)

/# exit (シェルからログアウト)

これで dokku 環境内に mydb という名前の PostgreSQL データベースを1つ作り、items という名前のテーブルを1つ定義する所まで用意できました。続いて、この mydb データベースと items テーブルを使ったウェブアプリケーションを dokku 内で動かします。


【dokku 内で PostgreSQL データベースに接続するアプリケーションを動かす】
dokku 内で PostgreSQL データベースを使うアプリケーションを動かします。今回は以下のサンプルを使います(上記で作成した items テーブルを使うアプリケーションです):
https://github.com/dotnsf/cnapp_postgresql


このアプリケーションの PostgreSQL と接続する部分は以下のように記述されています:
  :
  :
var database_url = 'DATABASE_URL' in process.env ? process.env.DATABASE_URL : settings.database_url; 
var pg = null;
if( database_url ){
  console.log( 'database_url = ' + database_url );
  pg = new PG.Pool({
    connectionString: database_url,
    idleTimeoutMillis: ( 3 * 86400 * 1000 )
  });
  :
  :


具体的な挙動としては、アプリケーション実行時の "DATABASE_URL" という環境変数値を参照し、値が設定されていたらその内容を接続文字列とみなして PostgreSQL サーバーに接続する、という実装内容になっています(説明は省略しますが、接続後に items テーブルを読み書きする内容になっています)。

なので、このアプリケーションが dokku 内で実行される時に、先ほど作成した mydb データベースへの接続文字列が環境変数 DATABASE_URL として定義されていればこのアプリケーションは正しくデータベースに接続して動く、ということになります。

この辺りは heroku ユーザーであればなんとなく「同じだ・・」とわかると思います。で、その環境変数を設定するためには dokku 内のアプリケーションとデータベースがリンクさえされていれば実現できるようになっています(この辺りはクラウドネイティブアプリケーションを開発する際の 12 factors と呼ばれるベストプラクティスに沿った仕様となっています)。

というわけで、まずは dokku にアプリケーションを追加し、データベースとのリンクを設定します。今回は "cnapp" という名前のアプリケーションを作ることにして、この "cnapp" アプリケーションと "mydb" データベースをリンクしておきます(これで cnapp アプリの実行時にデータベース mydb に接続するための接続文字列が環境変数 DATABASE_URL にセットされて起動します):
# dokku apps:create cnapp

# dokku postgres:link mydb cnapp

そして前回同様にこのサンプルアプリを Git clone して、リモート接続先に dokku を追加して、main ブランチを push します:
# git clone https://github.com/dotnsf/cnapp_postgresql

# cd cnapp_postgresql

# git remote add dokku dokku@withcorona.world:cnapp

# git push dokku main

ここまでのコマンドが正しく実行されていると http://cnapp.withcorona.world/ でアクセスできるようになります※:

2022060101


※稀にこの URL では想定していないページ(Nginx のデフォルトページなど)が表示されることがあります。その場合は http://cnapp.withcorona:8080/ のようにポート番号をつけてアクセスするとうまくいきます。その後に以下のコマンドを実行するとポート番号指定なしでも正しく表示できるようになります:
# dokku proxy:ports-add cnapp http:80:8080


これだけでも一応動きますが、ついでに(?) https 接続できるよう、Let's Encrypt プラグインの設定も行っておきます:
# dokku letsencrypt:enable cnapp

最後にウェブブラウザで https://cnapp.withcorona.world/ にアクセスして動作確認します:
2022060102


最初は何も登録されていませんが、名前(name)と価格(price)を入力して追加(Create)すると、そのデータが( PostgreSQL の)mydb データベースの items テーブルに登録され、一覧として表示されるようになります:
2022060103


以上、dokku でデータベース連携アプリケーションを作って動かすための設定でした。PostgreSQL 以外にも dokku には公式機能として MySQL や Redis 、ElasticSearch といったプラグインが用意されているので、クラウドで認証した結果をセッション共有するようなアプリケーションでも動かすことができると思います。




ある程度 heroku を使ったことがある人であれば、dokku は文字通りに heroku ライクなプライベート環境に感じることができると思います(ただ dokku だとほとんどの作業が CLI からのコマンドになる点が、ウェブ GUI で色々用意されている heroku とは異なる点です)。また Cloud Foundry と比較しても、Cloud Foundry では
$ cf push (app)

のようにしてアプリをデプロイしていましたが、dokku はほぼ同様にして、
$ git remote add dokku (app) (を一度実行してから)
$ git push dokku main

といった形でアプリのデプロイができるので、Cloud Foundry の代わりとしても使いやすい環境のように感じています。今回紹介した withcorona.world というドメインは実験用の捨てドメインなのでおそらくこの紹介記事でしか使うことはないと思っていますが、他の取得ドメイン(とちょっと規模の大きめな IaaS 環境)を使って自分のプライベート Cloud Foundry 環境を作って運用してみるつもりでいます。



唐突ですが、自分も使っている IBM Cloud の PaaS 機能の1つである Cloud Foundry ランタイムのサービス終了がアナウンスされました:
Cloud Foundry on IBM Cloud サービス提供終了のお知らせ


個人的にもサービス開始とほぼ同時期から使っていたユーザーとして、またソースコードから一発でクラウド環境で動かすことができる環境として使っていただけに、とても残念なニュースでした。実は少し前から知ってはいたのですが、公開できるのが今日からということで公にできずに悶々としていました(代わりにこのブログの公開準備を進めていました)。

で、IBM Cloud として推奨している移行先は Kubernetes 系サービスや Code Engine とされています:
IBM Cloud FoundryからCode Engineへのマイグレーションに関するベスト・プラクティスの紹介:サービス・バインディングとコード


Code Engine は Kubernetes の K-native をベースとしたコンテナ・ランタイム環境です。無料で使用可能なリソース枠も用意され、これまで Cloud Foundry で動いていたアプリケーションを比較的容易に移行できる環境となっております。


ただ、私自身はこの Code Engine に加えて、以下で紹介する dokku 環境も Cloud Foundry からの移行先としてアリだと思っています。Cloud Foundry よりも heroku と比較されることが多く、また残念ながら無料で運用できるわけではない(プライベート環境なので、そのサーバーリソースが必要)のですが、一方で独自ドメインが使える上にコンテナイメージを意識することなくソースコードから稼働環境を簡単に作れる、という点で相性は悪くないと思っています。 そんな意味も含めて、このタイミングで紹介させていただくことにしました。

予定としては2回に分けて、前半である今回はセットアップ部分を中心に、次回の後半でデータベースなど外部リソースとの連携について触れるつもりでいます。





昨年あたりから heroku を使うことが多くなってきました。無料でもある程度利用できるクラウドリソースが PaaS で提供され、Git と連動してアプリケーションのデプロイができるのが非常に便利です。

とても便利なので利用頻度が高くなっていき、あっという間に無料枠の限界が近づき・・・そして同時に無料枠の制約(インスタンスが使われていないと自動的に止まっちゃうとか、複数インスタンスで運用できないとか)を超えた使い方にも興味が出てきます。そうなってくるとパブリックな heroku 利用もいいけど、プライベートな heroku 環境を自由に使えたらいいなあ、というエンジニア欲も出てきます。

そんな背景もあって、「プライベート版 heroku 」という側面もある dokku を使ってみることにしました。dokku は内部的に docker を使って1台のサーバー内に仮想的な PaaS 環境を構築するものです。heroku (や Cloud Foundry )同様にビルドパックを使った git push デプロイが可能なので、手元のソースコードを簡単に(Dockerfile とか docker イメージ化などを意識せずに)ウェブ上に公開することもできます。1台のサーバーで運用することを想定しているため可用性という面では足りないと感じる面があるかもしれませんが、自分のように作ったアプリを試験的に公開する、という機会が多い場合に非常に重宝します。またプライベート PaaS はインフラ部分の管理を自分で行うことを意味していますが、その点では1台のサーバーの面倒だけみればよい、というのはある意味でアドバンテージにもなり得るものと考えています。

今回は以下の条件で環境を構築してみました:
 ・(2022/06/01 時点で最新の)dokku v0.27.5 を使用する
 ・IBM Cloud 上に Ubuntu サーバーを1台用意して、この中に dokku 環境を導入する
 ・GoDaddy.com で取得した独自ドメイン(withcorona.world)を使った PaaS 環境を作る

IBM Cloud 上に IaaS サーバー(Ubuntu 18.04)を用意するので、このサーバー料金が必要です。IBM Cloud である必要はありませんが、後述のように DNS の設定ができる IaaS 環境が必要です(Vultr.com では同様の設定ができることを確認しています)。なお、dokku は独自ドメインを使わなくても、sslip.io サービスを併用した疑似サブドメインを使って環境構築することもできますが、今回の紹介内容では軽く触れる程度とし、動作確認などは除外させていただきます。


【dokku 環境構築】
では早速 dokku 環境を用意して、アプリケーションを dokku 上で動かしてみます。まずは1度行う必要のある環境構築の手順を紹介します。

最初にクラウド上に Ubuntu サーバーを用意します。今回利用する dokku v0.27.5 では Ubuntu 18.04, Ubuntu 20.04, Ubuntu 22.04 までがサポート対象となっていましたが、古い記事で Ubuntu 18.04 だけがサポートされていたドキュメントを参照していたこともあり、自分も Ubuntu 18.04 を使うことにしました。なお Debian 系はサポート対象ですが、RHEL/CentOS 系は 2022/06/01 時点では Experimental 機能としての提供です。

(自分の場合は)IBM Cloud 上に Ubuntu 18.04 サーバーを1台用意します。スペックは 1 vCPU で 2GB RAM のものとしましたが、ここで選ぶスペックが自分の dokku 環境となるので、比較的多くのアプリケーションを動かす場合や、(多くの DB など)多くのリソースを使うことが想定される場合は少し大きめのサーバーを用意してください:
2022060101


サーバーが用意できたら次はネームサーバーをはじめとする DNS の設定が必要です(独自ドメインを使わない場合はここを無視して dokku のインストール作業まで進んでください)。まずは自分が使うサーバー(今回だと IBM Cloud のサーバー)で DNS を設定するために必要なネームサーバー設定を確認します。IBM Cloud の場合は以下のように
 ・プライマリサーバー: ns1.softlayer.com
 ・セカンダリサーバー: ns2.softlayer.com
を設定するように指定されていることが分かります。ここは皆さんが用意したクラウドサーバーのプロバイダーによって異なるので、自分の環境のネームサーバー設定を調べる必要があります:
2022060102


このネームサーバー設定を自分が取得した独自ドメインに適用します。今回の例では GoDaddy.com で取得した独自ドメインを使うので、GoDaddy.com の DNS 設定を変更することになります。 なお今回は "withcorona.world" という独自ドメインを使って、app1.withcorona.world, app2.withcorona.world, ... といった名称のアプリケーションを運用する前提とします。まずは自分がドメインを取得したベンダーのネームサーバー設定画面(DNS 設定画面)に移動します:
2022060103


ここでネームサーバーを変更します。GoDaddy.com の場合は DNS 管理画面の少し下に設定済みのネームサーバーが表示されている画面があるので、ここの「変更」ボタンをクリックします:
2022060104


そして先ほど確認したプライマリ/セカンダリサーバーを指定します。IBM Cloud の場合はプライマリが ns1.softlayer.com 、セカンダリが ns2.softlayer.com だったので、以下のように入力し、最後に「保存」ボタンをクリックします:
2022060105


この作業はこれまで使っていた独自ドメインの DNS 設定を大きく変えることになるので警告メッセージが表示されることがあります。内容を確認して「続行」します:
2022060106


無事に設定が変更されていることを確認します(この変更内容が実際に有効になるまで1時間程度かかることがあります):
2022060107


ネームサーバーの設定変更が出来たら、次は DNS を dokku 向けのものに変更します。新しい DNS 設定画面(今回の例では IBM Cloud の DNS 設定画面)に移動し、まずメインサーバーとなる独自ドメイン名(今回の例では "withcorona.world")と、 Ubuntu サーバーの IP アドレスを入力してドメインを登録します:
2022060108



そして残りの設定を行います。dokku を独自ドメインで利用するには、以下の内容を設定する必要があります:
レコードターゲット
A@(サーバーの IP アドレス)
CNAME*(独自ドメイン名)


なお IBM Cloud の場合は上述の設定ではなく、以下の設定が必要でした(* は CNAME レコードではなく A レコードとして、ターゲットは独自ドメイン名ではなく IP アドレスで指定する必要がありました):
レコードターゲット
A@(サーバーの IP アドレス)
A*(サーバーの IP アドレス)


最終的にこのような DNS 設定となりました:
2022060109


ここまでの作業で dokku 導入前の事前準備が完了です。ここからは dokku のインストール作業を紹介します。


まず SSH 等で Ubuntu サーバーのシェルにログインします。root 以外でログインした場合は "sudo -i" を実行するなどして root に切り替え、"apt update" と "apt upgrade" を済ませておきます:
$ sudo -i

# apt update -y

# apt upgrade -y

以下のコマンドを実行して dokku を(dokku v0.27.5 を)インストールします:
# wget https://raw.githubusercontent.com/dokku/dokku/v0.27.5/bootstrap.sh;

# DOKKU_TAG=v0.27.5 bash bootstrap.sh

2つ目のコマンドが完了(5~10分くらい)すると(docker ごと)dokku が導入されています。

独自ドメインを利用する場合はそのドメインを登録するため、以下のコマンドを実行します(最後の withcorona.world 部分に独自ドメイン名を指定します):
# dokku domains:set-global withcorona.world

このコマンド実行後に、/home/dokku/VHOST ファイルの中身が指定した独自ドメインになっていることを確認してください:
# cat /home/dokku/VHOST

withcorona.world (と表示されることを確認)

また独自ドメインを所有していない(使わない)場合は以下のコマンドを実行して、sslip.io サービスを使った疑似サブドメインを登録します(最後の 11.22.33.44 部分に Ubuntu サーバーの IP アドレスを指定します):
# dokku domains:set-global 11.22.33.44

# dokku domains:set-global 11.22.33.44.sslip.io


次に、実際に dokku を使う前に(dokku の git 利用時に必要な)秘密鍵と公開鍵のペアを登録する必要があります。普段使っている秘密鍵&公開鍵があればそれを使っても構いませんし、今回の作業のために新たに1ペア作って使っても構いません。鍵ファイルの作り方はこちらなどを参照してください。ここでは秘密鍵: id_rsa 、公開鍵: id_rsa.pub という2つの鍵ファイルが手元にあるものとします。

これらを dokku のサーバー環境に登録します。まずは sftp などでこれら2つのファイルを Ubuntu サーバーの /root/.ssh/id_rsa および /root/.ssh/id_rsa.pub となるよう転送しておきます。ここまでの作業ができているものとして以下の説明を続けます。

まず鍵ファイルはファイルパーミッションが正しくないと正しい挙動になりません。これら2つのファイルのパーミッションを 400 にしておきます:
# chmod 400 /root/.ssh/id_rsa*

これら2つのファイルを dokku 環境に登録します。まずは以下のコマンドを実行して秘密鍵を登録します:
# eval `ssh-agent`

# ssh-add -k ~/.ssh/id_rsa (秘密鍵のパスフレーズ入力を求められるので正しく入力します)

続けて公開鍵も登録します:
# cat ~/.ssh/id_rsa.pub | dokku ssh-keys:add admin

dokku 作業としてはここまででほぼ完了しているのですが、ついでに SSL(https) 接続を想定した準備もしておきましょう。以下の2つのコマンドで Let's Encrypt プラグインを導入・設定しておきます:
# dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git

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

実際にアプリケーションをデプロイする前の、dokku 環境構築に必要な(1回だけの)作業は以上で終わりです。


【dokku でウェブアプリを動かす】
では構築した dokku 環境を使って実際にウェブアプリケーションを稼働させてみます。ここからの内容は dokku に新しいアプリケーションをデプロイするたびに必要な作業です(ここよりも上で紹介した作業は環境構築時に1回行うだけ)。

まずはウェブアプリケーション(のソースコード)が必要ですが、今回は自分が作ったシンプルな「ハローワールド」アプリであるこれを使って紹介することにします(自分で作ったウェブアプリがある場合はそちらを使っていただいても構いません):
https://github.com/dotnsf/simpleweb


このサンプルはアプリというほどの内容ではないのですが、起動してアクセスすると以下のような画面が表示される、というものです。GitHub ページでも公開しているので、実際のサンプルを見たい場合はこちらにアクセスしてください:
https://dotnsf.github.io/simpleweb/

2022060112
(機能はこれだけ)


過去に heroku や Cloud Foundry、Docker、Kubernetes などのコンテナ環境でクラウドネイティブなアプリを作ったことがある人であれば問題ないと思いますが、ウェブアプリケーション起動時のポート番号を環境変数 PORT から取得するようにしている点にご注意ください(以下はこの simpleweb アプリの app.js のソースコード):
//. app.js
var express = require( 'express' ),
    app = express();

app.use( express.static( __dirname + '/web' ) );

var port = process.env.PORT || 8080;
app.listen( port );
console.log( "server starting on " + port + " ..." );

では改めてこのコードを例にして dokku 環境で動かすまでの手順を紹介します。まずは "dokku apps:create" コマンドで新しいアプリケーション(今回はアプリケーション名を simpleweb としています)を作成します:
# dokku apps:create simpleweb

次に git clone でソースコードを入手して、ディレクトリ内に移動します:
# git clone https://github.com/dotnsf/simpleweb

# cd simpleweb

(heroku と同様ですが)このソースコードに新しいリモート Git オリジン(dokku)として、dokku 内の Git リポジトリを追加します:
# git remote add dokku dokku@withcorona.world:simpleweb

そして今追加したオリジン dokku に main ブランチを git push します:
# git push dokku main

(秘密鍵のパスフレーズを聞かれるので入力する)

秘密鍵のパスフレーズを聞かれるので入力すると、GitHub ではなく dokku の内部ソースコードリポジトリにコードが Push され、該当ソースコード向けのビルドパック(今回の例であれば Node.js ビルドパック)を使ってソースコードが dokku 内のコンテナとしてデプロイされて起動します(1分くらいかかります)。この辺りの一連の流れは Cloud Foundry のものに近いです。


無事にデプロイが完了すると、http://(アプリ名).(ドメイン名)/ という URL でパブリックアクセスできるようになります。今回の例であれば http://simpleweb.withcorona.world/ です(この時点ではまだ https ではアクセスできません)※。 なおドメイン名を使わない場合は http://(アプリ名).xx.xx.xx.xx.sslip.io/ でアクセスできます(xx.xx.xx.xx は Ubuntu サーバーの IP アドレス):
2022060110


※稀にこの URL では想定していないページ(Nginx のデフォルトページなど)が表示されることがあります(そうなったりならなかったりします・・・)。Nginx のデフォルトページが表示されてしまう場合は http://(アプリ名).(ドメイン名):8080/ のようにポート番号をつけてアクセスするとうまくいきます。その後に以下のコマンドを実行するとポート番号指定なしでも正しく表示できるようになります:
# dokku proxy:ports-add simpleweb http:80:8080



https でアクセスできるようにするにはもう少しコマンドが必要です。単に https でアクセスできるようにするには(Let's Encrypt で証明書を取得して適用するだけであれば)以下の dokku コマンドを入力するだけです:
# dokku letsencrypt:enable simpleweb

更に証明書の自動更新までを有効にする場合は、続けて以下のコマンドを実行します(無効にする場合は "add" を "remove" にします):
# dokku letsencrypt:cron-job --add

ここまで正しく完了すると SSL 証明書が発行&適用されて https://simpleweb.withcorona.world/ でもアクセスできるようになります(ドメインを使わない場合はこの作業を省略しても https アクセスできます):
2022060111


なお、この方法で dokku にデプロイされたアプリケーションは heroku の無料利用時のように(アクセスが一定時間以上なかった場合に)自動停止することはなく、また以下のコマンドでスケールアウトすることもできます(この例ではインスタンス数=3):
# dokku ps:scale simpleweb web=3

利用想定規模と環境構築先の Ubuntu サーバーの規模を合わせて構築することで、非常に有用なプライベート PaaS 環境になりうると思いました。


【まとめ】
今回紹介した dokku を使ったプライベート環境は(無料ではありませんが) heroku ユーザーにとってもメリットを感じられるものだと思います。

今回は dokku 環境の構築と、ランタイム部分(ウェブアプリケーション部分)を dokku 環境でデプロイするまでの手順を紹介しました。次回は(これも heroku での作業とほぼ一緒だったりしますが)PostgreSQL などのデータベースサービスを dokku 内に作ったり、データベースと組み合わせてウェブアプリケーションを動かす方法を紹介する予定です。


【参照】
今回は最小限のインストール手順やコマンドだけを紹介しましたが、詳しくは以下も参照ください:

dokku 公式インストール手順
dokku デプロイコマンド


(2022/06/04 追記)
後編はこちら
http://dotnsf.blog.jp/archives/1080505175.html
 

このページのトップヘ