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

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

タグ:docker

ある意味、先日のこの記事の続きです:
「チームでアプリケーション開発を体験したい」、どうやる?


1つの案として(候補案4の)「オンラインエディタを使う」方法を紹介しました。ここで紹介したオープンソースのオンラインエディタ Eclipse Orion を Linux サーバーに、特に docker 環境下で簡単に導入する方法を紹介します。なお、ここでの「 docker 環境下」は正確には「x86_64 チップの docker 環境下」とさせてください(後述する docker イメージが linux/amd64 アーキテクチャ向けのため)。


まずは docker 環境を用意します。既に手元にあれば飛ばしていただいて構いませんが、こちらを参考いただくなどして環境にあった方法で docker エンジンが起動している状態にしておいてください:
Docker のインストール

今回紹介する方法ではこちらの Eclipse Orion 用 docker イメージを使わせていただきます:
https://hub.docker.com/r/cloudeity/orion


早速 docker pull して、・・・の前に、Eclipse Orion が参照する対象となるフォルダを自分の手元に用意しておきます。フォルダが空の状態から始めるのであれば空のフォルダを用意すればいいのですが、サンプルファイルが含まれた状態で始めるのであれば、対象ファイルがコピーされた状態のフォルダを用意しておく必要があります。

今回は以下のような index.html ファイル1つだけが用意されたフォルダを /tmp/web/ 以下に準備することにします(別のフォルダでも構いませんが、以下の内容を読み替えてください)。まず /tmp/web というフォルダを作ります(後でこのフォルダを Eclipse Orion の作業フォルダとします):
$ mkdir -p /tmp/web


そして以下の内容の index.html を作って、/tmp/web/ フォルダにコピーしておきます(/tmp/web/index.html ファイルを作ります):
<html>
Hello World.
</html>

改めて docker を使って Eclipse Orion をインストールします。まずは docker pull でイメージをダウンロードしておきます(初回のみ):
$ docker pull cloudeity/orion

次にコンテナ化して起動するのですが、その際に -v オプションで作業フォルダをボリューム指定します。今回のように /tmp/web を作業フォルダとする場合は以下のコマンドを実行します:
$ docker run -d --name orion -v /tmp/web:/opt/orion.client/modules/orionode/.workspace -p 8081:8081 cloudeity/orion

コマンドの実行に成功したらウェブブラウザで 8081 番ポートにアクセスします。成功していると /tmp/web フォルダがプロジェクトフォルダとなって、ウェブブラウザ画面から既存の index.html ファイルを編集したり、新規にフォルダやファイルを作成したり、編集したりができるようになります:

(同じマシンから http://localhost:8081 にアクセスした時の画面。他マシンからアクセスする場合は localhost 部分を IP アドレス指定にします)
2021022301

(index.html ファイルを選択するとエディタが開き、直接編集できます)
2021022302


Eclipse Orion 自体を終了するには docker コンテナを止めます:
$ docker stop orion




以前からこのテーマに興味があって、色々調べたり試したりしてもなかなかうまく行かず、半ばあきらめかけていた所で成功したので、その手順をまとめておきました。後述しますが、このテーマは今後多くの人が興味を持つ可能性が高いと思っているので、そんなみなさんのお役に立てれば。。


【背景】
おそらく最もメジャーなコンテナ技術の1つである docker は、docker hub に公開された多くのイメージが利用できる便利さとの相乗効果もあって、多くのコンテナ技術者に利用されています。自分も勉強用・動作検証用の簡易アプリイメージを docker hub に格納して使っています。

ただ最近になって docker 利用時に少し困ることも起こるようになりました。困っている内容を端的に表現すると「自分が公開しているアプリイメージは特定のアーキテクチャ向けに作られていて、異なるアーキテクチャの docker からは使えない」という問題でした。

もう少し詳しく説明します。自分はメインの開発環境としては Windows 10 を使っています。docker 環境は Windows 10 に Docker Desktop をインストールしてサーバーとして利用し、この環境に WSL2 の Ubuntu から docker コマンドを使って利用する、という使い方をしています。が、このメインマシン以外にも数台の PC があり、その中にはラズベリーパイも含まれています。ラズベリーパイにも docker を含めた開発環境が導入されていて、ラズベリーパイでも docker 環境が利用できます。

この環境が問題でした。Windows 10 や WSL2 の docker は linux/amd64 という「インテルアーキテクチャ CPU の 64 ビット Linux」向けの docker です。一方ラズベリーパイの docker は linux/arm/v7 という「ARM CPU の 32 ビット Linux」向けの docker です。これら2つのアーキテクチャは CPU が異なることもあり、バイナリ互換はありません。したがってどちらかで動くバイナリは、もう一方では動かないことになります。この問題は docker でも(後述の方法を使わない限りは)解決しておらず、一方の docker 環境で作成したイメージは、もう一方では動かない、という現象が発生していたのでした。このため Windows + WSL2 で docker イメージを作って docker hub に登録しても、ラズベリーパイからは使えないし、逆にラズベリーパイで docker イメージを作って docker hub に登録しても、Windows + WSL2 では使えない問題が発生していました。

この問題を解決するのが今回のテーマであるマルチ CPU アーキテクチャ対応 docker イメージです。名前の通りで複数のアーキテクチャ(今回であれば linux/amd64 と linux/arm/v7)に対応した docker イメージを作って docker hub に登録することで、docker pull を実行した環境のアーキテクチャに対応したイメージがダウンロードされ、Windows + WSL2 からもラズベリーパイからも docker 環境内で利用することができるようになるものです。このマルチ CPU アーキテクチャ対応 docker イメージの作り方を以下に紹介します。


【操作環境】
- Windows 10 Pro に Docker Desktop をインストール(docker エンジンは 19.03.13)。
- WSL2 に docker CLI をインストール(バージョン 19.03.13、19.03 以上であれば後述の buildx コマンドが使えます)

未確認ですが、docker は linux/amd64 の macOS 環境であっても以下の操作は可能です。ただ M1 と呼ばれる新しい環境の macOS から利用できるかどうかはわかりません。またラズベリーパイの docker や、CentOS7 環境に用意した docker 環境では後述の buildx コマンド自体は使えるのですが、他プラットフォーム向けのビルドに対応していないようで、操作環境としては事実上不適格でした。

docker CLI を WSL ではなく Windows から実行するケースも未確認ですが、このような open issue もあって、もしかすると動かない可能性もあるのではないかと思っています:
https://github.com/docker/for-win/issues/4991


【操作前の準備(ウェブアプリと docker hub アカウントの用意)】
最初に docker イメージを作る際のアプリケーションを用意します。もちろん自分でアプリを開発できる方はそれを使っていただいても構いません。そうでない人向けに自分が以下で解説する際に使った hostname アプリのソースコードを公開しているので、こちらをダウンロードするか git clone して使って試していただいても構いません:
https://github.com/dotnsf/hostname

このアプリは Node.js で記述されたシンプルなアプリで、以下のような挙動です(もちろん特定のアーキテクチャに依存するような内容ではありません):
- 8080 番ポートで HTTP リクエストを待ち受け
- "/" にアクセス(例えば同じホストからであれば http://localhost:8080/ にアクセス)すると、/etc/hostname ファイルの内容をそのまま text/plain で返す


普通の Windows(WSL) や macOS 、ラズペリーパイを含む Linux 環境では /etc/hostname にはそのマシンのホスト名称が記載されているのが一般的ですが、特に docker 環境においては /etc/hostname には稼働中のコンテナIDが記載されます。したがって k8s などを併用して複数インスタンスを起動してこのアプリのイメージを実行してアクセスした場合、アクセス結果からどのコンテナに振り分けられて処理されたのかが視覚化されて便利なので、そういった動作確認時などに使っていたアプリでした。
//.  app.js
var express = require( 'express' ),
    fs = require( 'fs' ),
    app = express();

app.get( '/', function( req, res ){
  res.contentType( 'text/plain; charset=utf-8' );
  fs.readFile( '/etc/hostname', "utf-8", function( err, text ){
    if( err ){
      res.write( JSON.stringify( err, 2, null ) );
      res.end();
    }else{
      res.write( text );
      res.end();
    }
  });
});

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


ちなみに Dockerfile は以下のような内容です:
# base image
FROM node:12-alpine

# working directory
WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 8080
CMD ["node", "app.js"]


ベースイメージに node:12-alpine を指定しています。ここで注意点として、ベースイメージにアーキテクチャ依存の指定を含まないようにする必要があります。例えばラズペリーパイ向けのイメージを明示的に作ろうとすると、ここは
FROM arm32v7/node:12-alpine

のようにラズベリーパイで使われている arm32v7 アーキテクチャ向けの node:12-alpine イメージをベースとする、という指定を明示的にすることも可能です。が、このような指定をしてしまうとインテル CPU アーキテクチャ向けのイメージを作る際のエラーの原因となってしまいます。このような特定アーキテクチャ向けのベースイメージ指定はしないようにしてください。


そして、もしもまだ docker hub のアカウントをお持ちでない場合は、実際の操作の最後に必要になるので、今のうちに docker hub をアカウントを取得しておいてください。取得済みの人はログインできるように ID とパスワードを思い出しておいてください。


【操作内容】
では実際にアプリケーションファイルと Dockerfile からマルチCPUアーキテクチャー対応 docker イメージを作って docker hub に push してみます。なお今回は linux/amd64(インテルCPU向け 64 ビット Linux)と linux/arm/v7(ラズベリーパイ)の2種類のアーキテクチャー向けにイメージを作る例を紹介しますが、同様の操作で linux/ppc64le(POWER アーキテクチャー向け 64 ビット Linux)や linux/s390x(zLinux)を対象としたマルチCPUアーキテクチャー対応イメージを作ることも可能です。


(追記 2021/02/15)
その後イメージをアップデートし、現在は linux/arm/64, linux/ppc64le, linux/s390x を含めた5アーキテクチャ対応イメージを公開しています
(追記終わり)


まず最初に、今回行うマルチCPUアーキテクチャー対応イメージのビルドは、docker の試験的機能の1つであり、これを行うには試験機能を有効にしておく必要があります。そのためターミナルや WSL2 のコンソールを開き、~/.bashrc に以下の行を追加するなどして、環境変数 DOCKER_CLI_EXPERIMENTAL の値を enalbed に設定しておいてください:
export DOCKER_CLI_EXPERIMENTAL=enabled

設定後、ターミナルを一度閉じてから再度開きます(これで上記環境変数の設定が有効な状態でターミナルが開きます)。

確認のため、以下のヘルプコマンド("docker --help")を実行します。結果が下図のように buildx* といった * が付いた試験機能を含めたコマンドリストのヘルプが表示されれば試験機能が有効になっています:
$ docker --help

Usage:  docker [OPTIONS] COMMAND

A self-sufficient runtime for containers

Options:
      --config string      Location of client config files (default "/home/dotnsf/.docker")
  -c, --context string     Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and
                           default context set with "docker context use")
  -D, --debug              Enable debug mode
  -H, --host list          Daemon socket(s) to connect to
  -l, --log-level string   Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info")
      --tls                Use TLS; implied by --tlsverify
      --tlscacert string   Trust certs signed only by this CA (default "/home/dotnsf/.docker/ca.pem")
      --tlscert string     Path to TLS certificate file (default "/home/dotnsf/.docker/cert.pem")
      --tlskey string      Path to TLS key file (default "/home/dotnsf/.docker/key.pem")
      --tlsverify          Use TLS and verify the remote
  -v, --version            Print version information and quit

Management Commands:
  app*        Docker Application (Docker Inc., v0.8.0)
  builder     Manage builds
  buildx*     Build with BuildKit (Docker Inc., v0.4.2-tp-docker)
  config      Manage Docker configs
  container   Manage containers
:
:

ではここからは実際にマルチCPUアーキテクチャー対応 docker イメージを作るための操作を行います。まず最初に "docker buildx ls" を実行して現在のビルダーインスタンスの一覧を確認します:
$ docker buildx ls

NAME/NODE    DRIVER/ENDPOINT             STATUS  PLATFORMS
default *    docker
  default    default                     running linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6

default という名前の標準ビルダーインスタンスが見つかります(横の * は現在選択されているビルダーインスタンスを示します)。このビルダーインスタンスは linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6 のビルドに対応している、ということがわかります。

※なお CentOS7 版の docker や、ラズベリーパイ版の docker でこのコマンドを実行すると buildx コマンドそのものは利用できるのですが、ビルドの対応プラットフォームがあまりに少なく、クロスビルド環境としては不十分であることがわかります。なので今回は Windows(WSL) 版の docker コマンドを使って作業します。

ということはこの default ビルダーインスタンスを使うことで linux/amd64 と linux/arm/v7 向けイメージをビルドできそうに見えるのですが、実際にやってみるとこのエラーになってしまいます。理由や原因はよくわからないのですが、新しいビルダーインスタンスを作成して使うことで回避できそうなので、今回はその方法を紹介します。


といった事情もあり、まずはクロスビルド用のビルダーインスタンスを作成します。以下のコマンドを実行してビルダーインスタンスを(下の例では mybuilder という名前で)新たに作成&選択&起動します:
$ docker buildx create --name mybuilder

$ docker buildx use mybuilder $ docker buildx inspect --bootstrap

この時点でのビルダーインスタンス一覧は以下のようになります。mybuilder が作成されて選択(* 印)され、起動(STATUS が running) の状態になっているはずです:
$ docker buildx ls

NAME/NODE    DRIVER/ENDPOINT             STATUS  PLATFORMS
mybuilder *  docker-container
  mybuilder0 unix:///var/run/docker.sock running linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
default      docker
  default    default                     running linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6

ここまでの設定で新たに作成した mybuilder ビルダーインスタンスを使って docker イメージを(クロス)ビルドする準備が整いました。では早速クロスビルドしようと思うのですが、その前に少し注意が必要です。

ただ単に linux/amd64 と linux/arm/v7 向けにクロスビルドを実行しようとすると、アプリケーションソースと Dockerfile の存在するディレクトリで以下のコマンドを実行することになります(エラーになるので実行しなくていいです):
$ docker buildx build --platform linux/amd64,linux/arm/v7 -t dotnsf/hostname --load .

クロスビルドの対象プラットフォームを --platform オプションに続けて指定します(上の例では linux/amd64,linux/arm/v7)。また -t オプションに続けてイメージの名称(上の例では dotnsf/hostname)を指定するのですが、一般的には (docker hub のログイン名)/(アプリケーション名) という形で指定します。僕の場合はそれが dotnsf/hostname となるわけですが、ここは皆さんの環境に合わせて変更してください(僕の hostname アプリを使う場合は dotnsf の部分だけを皆さんの docker hub ログイン名に変えてください)。

コマンドとしてはこれで指定したプラットフォーム用の docker イメージがビルドされる・・・はずなのですが、docker は自分のビルド環境(今回の場合は linux/amd64)と異なるプラットフォーム( linux/arm/v7)のイメージを出力できないという制約があります。そのためこのコマンドをそのまま実行してもエラーとなってしまうのでした。

この制約を回避するため、docker のクロスビルド結果をローカル docker ではなく、直接 docker hub に向けて出力することにします。普段はこういう使い方はあまりしないと思うのですが、マルチ CPU アーキテクチャー対応 docker イメージを作る場合の例外方法だと認識する必要がありそうです。

そのためビルドを実行する前に docker hub へログインします。"docker login" コマンドを実行し、自分の docker hub アカウントのユーザー名とパスワードを指定してログインします:
$ docker login

Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: dotnsf
Password: ********
Login Succeeded

ログイン成功後に改めてクロスビルドを行います。今度は上述のコマンドの最後のオプションとして指定した --load の代わりに --push オプションを指定します(これでビルド結果を直接 docker hub に送信します):
$ docker buildx build --platform linux/amd64,linux/arm/v7 -t dotnsf/hostname --push .

このコマンドの実行にはかなりの時間がかかります。通信環境やビルド内容にもよりますが、自分の場合は hostname アプリを2つのプラットフォーム向けにビルドして docker hub へ送信し終わるまで約10分かかりました。

このコマンドが成功すると docker hub にビルドしたイメージが保存されているはずです。自分が上のコマンドを実行した結果はこちらから確認できます:
https://hub.docker.com/r/dotnsf/hostname

20210213

↑Tags タブを参照すると、このイメージが linux/amd64 及び linux/arm/v7 の両プラットフォームに対応していることが確認できます。

これで目的のマルチ CPU アーキテクチャー対応 docker イメージをビルドして docker hub に push することができました。今後、この mybuilder ビルダーインスタンスをそのまま使い続ける場合はそのままでもいいのですが、元の default ビルダーインスタンスに戻して使い続ける場合は以下のコマンドを実行しておいてください:
$ docker buildx stop mybuilder  (mybuilder 停止)

$ docker buildx use default  (default を選択)

$ docker buildx rm mybuilder  (mybuilder を削除する場合はこれも)


【動作確認】
では実際に上で作成したイメージで動作確認してみます。linux/amd64 か linux/arm/v7 の docker 環境(可能であれば両方)がある方も以下のコマンドで動かすことができるので是非お試しください。上述のビルダーインスタンス作成などは不要です。

まずは linux/amd64 の docker 環境で以下のコマンドを実行します:
$ docker run -d --name hostname -p 8080:8080 dotnsf/hostname

初めて実行した場合は dotnsf/hostname イメージのダウンロードから始まるので起動まで少し時間がかかるかもしれませんが、ダウンロード完了後(2度目以降の実行であればすぐに)コンテナが作成されて実行され、8080 番ポートで HTTP リクエストを待ち受ける状態になります。

起動後、以下のコマンドでコンテナ ID を確認しておきます(以下の例では c4335ca15762):
$ docker ps

CONTAINER ID   IMAGE             COMMAND                  CREATED          STATUS          PORTS                    NAMES
c4335ca15762   dotnsf/hostname   "docker-entrypoint.s…"   32 seconds ago   Up 29 seconds   0.0.0.0:8080->8080/tcp   hostname

curl コマンドでこのアプリケーションにアクセスしてみます。このコンテナの /etc/hostname の値が出力されます:
$ curl http://localhost:8080/

c4335ca15762

先程確認したコンテナ ID と同じ値が表示されるはずです。というわけで、この linux/amd64 プラットフォームでは dotnsf/hostname イメージが期待通りに動きました。


続けて、全く同じコマンドを linux/arm/v7(ラズベリーパイ)環境の docker でも実行してみます。まずは dotnsf/hostname イメージを指定してコンテナを作成&起動します:
$ docker run -d --name hostname -p 8080:8080 dotnsf/hostname

起動後、以下のコマンドでコンテナ ID を確認しておきます(以下の例では 6a0b27c4ad76):
$ docker ps

CONTAINER ID   IMAGE             COMMAND                  CREATED          STATUS          PORTS                    NAMES
6a0b27c4ad76   dotnsf/hostname   "docker-entrypoint.s…"   29 seconds ago   Up 27 seconds   0.0.0.0:8080->8080/tcp   hostname

curl コマンドでこのアプリケーションにアクセスしてみます。このコンテナの /etc/hostname の値が出力されます:
$ curl http://localhost:8080/

6a0b27c4ad76

先程確認したコンテナ ID と同じ値が表示されるはずです。というわけで、この linux/arm/v7 プラットフォームでも dotnsf/hostname イメージが期待通りに動きました。

したがって、全く同じ docker イメージ(dotnsf/hostname)を指定して、linux/amd64 でも linux/arm/v7 でも同様に動くコンテナを起動することができました。マルチ CPU アーキテクチャー対応 docker イメージが正しく作れていることが確認できました。

なお作成したコンテナの起動を止める場合は以下のコマンドを実行してください(両環境共通):
$ docker stop (コンテナID)

起動を止めたコンテナを削除する場合は続けて以下のコマンドも実行してください(両環境共通):
$ docker rm (コンテナID)


【感想】
このマルチ CPU アーキテクチャー対応 docker イメージはまだ試験機能ということもあり、準備にも色々面倒な手順が必要です。ただ異なるアーキテクチャでも同じコマンドでアプリケーションを起動できることができるのが docker の強みでもあると思っています。今回は未確認ですが、今後 M1 mac 環境でも docker が正式対応したりすると、マルチ CPU アーキテクチャー対応 docker イメージの需要も増えてゆくものと想像しています。そうなった場合に備えて今のうちに勉強できました。


【参考】
https://docs.docker.jp/docker-for-mac/multi-arch.html


このケースというか、この利用パターンでの情報がググっても意外と見つからなかったので、自分でメモを残します。

なお、以下の内容は x86_64 アーキテクチャの docker 環境が導入済みであるという前提で説明します。


【やりたかったこと】
やりたかったことは、
- クラウドなどでデータベース・サーバー(今回は PostgreSQL)を利用して、
- そのデータベース・サーバーを GUI 管理するためのクライアント(今回は pgadmin4)をローカルの docker コンテナとして起動する

というものです。環境自体は docker なしでももちろん可能ですが、自分の開発環境に余計なものを入れたくなかったので、docker コンテナで ON/OFF できる形で用意できれば理想かな、と思い、docker を使って構築しようと考えました。

この場合、前者(PostgreSQL)は既に起動している前提となるので、後者(pgadmin4)をどうやって docker 内に用意すればよいか、が課題となります。

クラウドのデータベース利用時ではそれほど珍しくない利用パターンだと思っているのですが、ググってみると(docker-compose 等を使って)ローカル docker コンテナに PostgreSQL と pgadmin4 の両方を起動して利用するケースでの手順が多く見つかり、pgadmin4 だけを単独でローカル docker に起動して利用する手順が意外と見つかりませんでした。 というわけで、以下は自分で調べた内容の備忘録メモです。


【調べたこと】
pgadmin は dpage/pgadmin4 イメージを使わせていただくことにします。ページ概要だけを見るとコンテナ起動時にどんなパラメータをどのように指定すればよいかわかりにくかったのですが、結論としてはこんな感じでパラメータを指定すればよさそうでした:
パラメータ指定方法パラメータの意味
PGADMIN_DEFAULT_EMAIL 環境変数 pgadmin ログイン時のユーザー名
PGADMIN_DEFAULT_PASSWORD 環境変数 pgadmin ログイン時のパスワード
ポートフォワード docker run 時の -p パラメータ 80 番(http)への内部ポートをどのポート番号からフォーワードするか


【動かしてみる】
ターミナルやコマンドプロンプトを起動し、上述の内容を docker コマンドで指定して dpage/pgadmin イメージをコンテナ化します:
$ docker pull dpage/pgadmin

$ docker run --name pgadmin4 -e "PGADMIN_DEFAULT_EMAIL=dotnsf@xxxx.com" -e "PGADMIN_DEFAULT_PASSWORD=P@ssw0rd" -d -p 8000:80 dpage/pgadmin4

上の例では PGADMIN_DEFAULT_EMAIL に dotnsf@xxxx.com を、PGADMIN_DEFAULT_PASSWORD に P@ssw0rd を指定し、ポートフォワードのポート番号は 8000 番に指定して、pgadmin4 という名前でコンテナを起動しています。必要に応じて上述部分を変更して使ってください。


今回は 8000 番ポートで起動しているので、ウェブブラウザで 8000 番ポートを指定して http://localhost:8000/ にアクセスします:
2021021001


pgadmin(4) が起動していれば上記のような画面になります。ここで docker コンテナ起動時に環境変数で指定したログインユーザー名とパスワードを指定し、必要であれば言語を "Japanese" に指定してログインします:
2021021002


無事に pgAdmin にログインできました。実際にクラウドで起動中のデータベース・サーバーに接続するにはトップページから「新しいサーバを追加」を選択します:
2021021003


起動中の PostgreSQL データベース・サーバーに接続するためのホスト名やポート番号等の情報を入力して保存します:
2021021004


正しい情報が入力できているとデータベース・サーバーに接続でき、統計情報やスキーマ、実際のデータを pgadmin 画面から参照できるようになります:
2021021005


使わない時はコンテナを止めてしまえばポートも開放されるし、コンテナを削除しなければ接続情報なども保存されるので、気軽に ON/OFF できる pgadmin 環境が構築できました。


以前にとある人から
 (例えば仮想マシン環境と比較して)コンテナ環境のデメリットはなんですか?
と質問され、一瞬返答に詰まってしまったことがありました。メリットを考えたことはあったけど、積極的にデメリットを考えたことがなく、即答できませんでした。ちゃんと正しく理解していないとなかなか難しい質問だと思っています。

改めて時間のある時に考えてみるといくつか思いつきます。まあ「デメリット」といえるかどうかはともかく、「VMでできてコンテナでできないこと」はいくつかあります。

その一つが "cron" ジョブだと思っています。特定時刻とか、何分おきにとか、実行タイミングのスケジュールを決めた上で実行する機能です。例えば kubernetes であれば CronJob を使って実現するなど、厳密には「コンテナで実現できない」わけではないのですが、そのコンテナ環境に合わせた対応が必要になるのはそれはそれでデメリットになりえますよね。

一方、アプリケーション開発レベルでは、これらのスケジュールジョブを併用することでアプリケーションとしての必要な機能を実現することは珍しくありません。1分毎にどこかからデータを取得して更新するとか、毎日○時に自動的にバックアップを取得するとかといった場合です。それらの機能が必要なアプリケーションをコンテナ環境で動かす可能性がある中で実装する場合、どういった方法を検討する必要があるでしょうか?


その答えの1つが「アプリケーションレベルで(アプリケーションの機能の一部として) cron ジョブを実装する」方法です。Node.js アプリケーションの場合は Node-Schedule ライブラリを使うと簡単に実現できそうだったので、その内容を以下で紹介します:
2020071200



Node-Schedule は Node.js で使えるライブラリで、cron ライクなスケジュールジョブを比較的簡単に(setTimeout とかを意識することなく)実現できます。またスケジュールの定義フォーマットは cron のものと互換性があるので、crontab に1行追加する感覚で、アプリケーション内にスケジュールジョブを追加・更新・削除できるものです。アプリケーションの中でスケジュールジョブを定義できるので、アプリケーションの実行環境(実機とか、VM とか、コンテナとか、・・)を意識する必要もありません。

例を1つ記述しておきます。以下のコードで1分おきにコンソールにメッセージを表示するウェブアプリケーションが作成できます(Node-Schedule に関係している部分のみ赤字):
// app.js
var schedule = require( 'node-schedule' );
var express = require( 'express' );
var app = express();

//. 毎分実行
schedule.scheduleJob( '* * * * *', function(){
  console.log( 'running a task every minute' );
});


app.get( '/', function( req, res ){
  res.write( JSON.stringify( { status: true }, null, 2 ) );
  res.end();
});


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

上記コードから赤字部分を抜くと、ごくシンプルな Node.js + Express のウェブアプリケーションになるのがわかると思います。つまり Node-Schedule に関係しているのは赤字部分の実質4行だけです。

で、その赤字部分で何をしているのかというと、まず先頭行で require() して Node-Schedule ライブラリのモジュールを呼び出します:
// app.js
var schedule = require( 'node-schedule' );

そして scheduleJob() メソッドを使ってジョブを(イメージとしては cron に)登録します:
//. 毎分実行
schedule.scheduleJob( '* * * * *', function(){
  console.log( 'running a task every minute' );
});

この第一パラメータは cron に登録する時に指定する時刻フォーマットと互換性のある文字列表現を使います。'* * * * *' は「毎分実行」を意味しています。

そして第二パラメータには該当時刻になったら実行するコールバック関数を指定します。上の例では 'running a task every minute' とコンソールに表示するだけの内容にしていますが、実際にはここに crontab の最後に指定するコマンドを登録することになります。これで1分ごとに 'running a task every minute' という文字列がコンソールに表示され続けるジョブが登録できたことになります。


なお、一度登録したジョブをキャンセルするには登録時の scheduleJob() メソッドの実行結果オブジェクトを受け取り、そのオブジェクトの cancel() メソッドを実行します:
//. 毎分実行
var job = schedule.scheduleJob( '* * * * *', function(){
  console.log( 'running a task every minute' );
});

  :

job.cancel(); //. 登録したジョブをキャンセル

ジョブの実行条件や実行内容を更新する場合は一度キャンセルしてから再登録することで実現できます。


最近のクラウド環境は PaaS 化が進み、どういうコンテナ環境を使っているのかよくわからないことがあるかもしれません。アプリケーションを Node.js で記述するという条件はありますが、この方法で実装していればコンテナ環境に依存しないスケジュールジョブが実現できそうです。


不定期(要するに「ふと思い立ったタイミング」)で LinuxONE の紹介をしています。LinuxONE はメインフレームのハードウェア上で動く Linux です。そういえば LinuxONE で docker って使えるんだろうか?使えるとしたらどのあたりまで使える?? ということを確認したくなって、久しぶりに LinuxONE コミュニティクラウドの環境構築をしてみました。なお、以下の内容は 2019/05/27 時点の状況を紹介したものです。
2019052700


LinuxONE コミュニティクラウドは最大 120 日間無料で利用可能な LinuxONE の環境です。パブリックな IP アドレスが割り振られるので、インターネットから利用することもできます。FAQ も参照ください:
https://developer.ibm.com/linuxone/resources/faq/


LinuxONE コミュニティクラウドの申し込み方法は以前(2017年)のブログエントリで紹介したものとほとんど変わっていません。こちらを参照してください:
http://dotnsf.blog.jp/archives/1063515821.html

ここで記載されている情報と異なる点として、2019/05/27 現在では OS として RHEL 6.x を選択することはできなくなっています。そのため今回は RHEL 7.x を選択しました(RHEL 7.6 が導入されました)。またサーバーのスペック選択肢が廃止され、常に 2CPU + 4GB メモリ + 50 GB ディスク の環境が提供されるようになっていました。

仮想サーバーができたら IP アドレスとサーバー作成時に作ってダウンロードした鍵ファイル(*.pem)を指定して linux1 ユーザーでログインします:
2019052701


ログインできました。実際に試していただくとわかるのですが「ほぼ x86_64 版の RHEL 7.6」です。明示的にアーキテクチャを確認しないと s390x 版であることに気づかないかもしれません。

そしてこの後の docker 環境構築の手順に備えて root ユーザーに切り替えます:
$ sudo -i
#


さて、docker および docker-compose をこの LinuxONE 環境に導入していきます。手順そのものはこちらで紹介されているものをほぼそのまま使うのですが、2019/05/27 現在の環境では記載そのままの手順では途中でエラーになってしまい、導入できませんでした。エラー回避のため、少し異なる手順で導入します(異なる部分をで記載しています)。
https://github.com/IBM/Cloud-Native-Workloads-on-LinuxONE/blob/master/README-ja.md


【docker の導入】
まずインストールする docker の s390x 向けパッケージファイルをダウンロードします。RHEL 7.3 以上向けに docker 17.05 の CE(Community Edition)版が用意されているのでダウンロードします:
# wget ftp://ftp.unicamp.br/pub/linuxpatch/s390x/redhat/rhel7.3/docker-17.05.0-ce-rhel7.3-20170523.tar.gz

ダウンロードしたアーカイブファイルを展開し、バイナリを /usr/local/bin/ 以下にコピーします:
# tar -xzvf docker-17.05.0-ce-rhel7.3-20170523.tar.gz
# cp docker-17.05.0-ce-rhel7.3-20170523/docker* /usr/local/bin/

2019/05/27 時点では標準状態では /usr/local/bin にパスが通っていませんでした。このままだと docker コマンドがそのまま使えないので、パスを通しておきます:
# export PATH=$PATH:/usr/local/bin

これで docker コマンドが使えるようになったので、docker デーモンを起動します:
# docker daemon -g /local/docker/lib &


【docker-compose の導入】
続いて docker-compose もインストールします。実はこちらがドキュメント通りにいかない部分が多く、少し厄介でした。

手順としては pip を使って docker-compose をインストールします。そのため pip を先にインストールするのですが、pip をインストールするための依存ライブラリを先に導入します:
# yum install -y python-setuptools

そして pip をインストールします:
# easy_install pip

インストールした pip を使って、まず backports.ssl_match_hostname をアップグレードするのですが、このコマンドをドキュメント通りに入力すると既に導入済みの環境とのコンフリクトが起こってエラーになってしまいました。というわけで --ignore-installed オプションを付けて実行します:
# pip install backports.ssl_match_hostname --upgrade --ignore-installed

そして pip で docker-compose をインストール・・・するのですが、ここでもドキュメントのまま実行すると依存関係ライブラリが足りないというエラーになってしまいます。そのためまずは依存ライブラリを導入しておきます:
# yum install python-devel
# yum install gcc libffi-devel openssl-devel
# pip install glob2

改めて pip で docker-compose をインストールします。ここでもドキュメントそのままの指定だとエラーになってしまうので、バージョン 1.13.0 を明示してインストールします:
# pip install docker-compose==1.13.0

これで docker および docker-compose が LinuxONE 環境にインストールできました:
# docker -v
Docker version 17.05.0-ce, build 89658be

# docker-compose -v
docker-compose version 1.13.0, build 1719ceb

2019052702


【WordPress の導入】
では導入した docker と docker-compose を使ってコンテンツ管理システムである WordPress を導入してみます。

テキストエディタ(vi とか)を使うなどして、docker-compose.yml というファイルを以下の内容で作成して保存します:
version: '2'

services:

  wordpress:
    image: s390x/wordpress
    ports:
      - 80:80
    environment:
      WORDPRESS_DB_PASSWORD: example

  mysql:
    image: brunswickheads/mariadb-5.5-s390x
    environment:
      MYSQL_ROOT_PASSWORD: example

このファイルは docker-compose 向けの定義ファイルで wordpress と mysql という2つのコンテナ環境を定義しています。wordpress は PHP, Apache HTTPD, および WordPress が含まれる s390x 向けのイメージで 80 番ポートで HTTP リクエストを待ち受けます。また mysql は MySQL(正確には mariaDB)が含まれる s390x 向けイメージです。これら2つのイメージから2コンテナ環境を作り出して WordPress として挙動するようにしています。

では、docker-compose と、この docker-compose.yml ファイルを使って docker コンテナを起動します:
# docker-compose up -d

(必要に応じて)イメージをダウンロードし、イメージからコンテナが作られて起動します。プロンプトが戻ってきたら、docker ps コマンドを実行して wordpress と mariadb の2つのコンテナが起動していることを確認します:
# docker ps
CONTAINER ID        IMAGE                              COMMAND                  CREATED             STATUS              PORTS                NAMES
65af9dfa6ee9        s390x/wordpress                    "docker-entrypoint..."   3 hours ago         Up 3 hours          0.0.0.0:80->80/tcp   dockers_wordpress_1
3eb78f3ef1c1        brunswickheads/mariadb-5.5-s390x   "/docker-entrypoin..."   3 hours ago         Up 3 hours          3306/tcp             dockers_mysql_1

起動が確認できたらウェブブラウザから(LinuxONE 環境の) IP アドレスを指定してアクセスします(http://xxx.xxx.xxx.xxx/)。以下のような WordPress の環境設定画面になれば成功です:
2019052801


言語やユーザーID、パスワードなどの設定が完了すると管理画面にログインできるようになりました:
2019052802


この時点でユーザーページにもアクセス可能です:
2019052803


とりあえずメインフレーム上の Linux に docker & docker-compose 環境を構築して WordPress を導入することができました!


このページのトップヘ