20201126のdockerに関する記事は12件です。

MySQL 文字コードによるINSERT時のエラー(ERROR 1366 (HY000):....)

はじめに

Dockerを使用して立てたMySQLのコンテナ上に自分以外の方がNestJSとtypeORMで書いてくれたmigrationコードを実行してデータベースを構築したところダミーデータ作成時に日本語の部分が以下のエラーで弾かれました。
ERROR 1366 (HY000): Incorrect string value:
このエラーに対してどのように対処したかの一例を述べます。 (ぽんこつすぎて6時間もってかれましたー。。。)

ここで紹介するのはデータベースの文字コードを変えて再起動するだけでは解決できなかった事例です。

使用していたdocker-compose.ymlファイル
作成したデータベースの情報がコンテナremove時に失われないようにローカル環境に保持されるようにvolumesを設定しています。

version: '3'

services:
  mysql:
    image: mysql:5.7
    volumes:
      - "./mysql:/var/lib/mysql"
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=設定するパスワード
      - MYSQL_DATABASE=データベース名
    ports:
      - 3306:3306


  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOST=mysql
      - PMA_USER=root
      - PMA_PASSWORD=設定するパスワード
    ports:
       - 8080:80
    volumes:
       - /sessions

とりあえず詳しいこと抜きで解決した方法

  1. MySQL初期設定用のmy.cnfを文字コードがutf8mb4で動作するように記述し作成し任意の場所に配置
  2. 作成したmy.cnfがdocker-compose.ymlでコンテナ上の/etc/my.cnfとして配置されるようにvolumesを記述
  3. エラーが発生しているデータベースを削除
  4. 同名データベースを作成
  5. migrateを実行(ここはmigrateコードなどの自動生成コードを作っていない場合はもう一度地道にテーブルを作り直してください)

やったこと

Dockerの公式MySQLの文字コードをutf8mb4に設定する

↑の記事を参考にした

こちらの記事では、commandでmysqldを実行するように書いてあるがここでもエラーが出て実行できなかった。
どうやらmysqldコマンドはrootユーザーでは使用できないようです。なのでちょっとめんどくさかったですが、my.cnfを作業ディレクトリで作成しvolumesで紐付け"my.cnf:/etc/my.cnf" でローカルに作成したmy.cnfをコンテナ上に配置するようにしました。
とりあえず/etcの下に配置すれば動作する感じです。

my.cnfの内容で必須なのはcharacter-set-serverとdefault-character-setっぽい

尚、上の記事を参考に作成したmy.cnfではうまく動かなかったです。なので結局参考にしたのはこれ
MySQLで文字コードを直す時のmy.cnfの書き方 - 鶏口牛後な日々
結果的に作成したmy.cnfは以下のとおりです。

[mysqld]
character-set-server=utf8mb4
explicit-defaults-for-timestamp=1
general-log=1
general-log-file=/var/log/mysql/mysqld.log

[mysql]
default-character-set=utf8mb4

出来上がったdocker-compose.ymlは以下の通り

version: '3'

services:
  mysql:
    image: mysql:5.7
    volumes:
      - "./mysql:/var/lib/mysql"
      - "./my.cnf:/etc/my.cnf"         ←--- これを追加しました!
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=設定するパスワード
      - MYSQL_DATABASE=データベース名
    ports:
      - 3306:3306


  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOST=mysql
      - PMA_USER=root
      - PMA_PASSWORD=設定するパスワード
    ports:
       - 8080:80
    volumes:
       - /sessions

すでにデータベースを作成しているときはデータベース自体に文字コードを設定してしまっておりMySQLの設定を変更しても適用されないため、データベースの設定を変更する

MySQLの日本語文字化け回避!文字コードを確認&変更する方法 | サービス | プロエンジニア

これをやってもまだ動かなかった

データベースのテーブルを確認するとcollationが例のutf8じゃないやつになっていた。
なので、データベース自体を一度ドロップしてデータベースからテーブルまですべて削除し、migrate(データベースとテーブルの作成)しなおしたところcollationが変更され日本語がINSERTできるようになりました。

まとめ

データベースの初期設定さえしっかりやっていればこんなことにはならなかったですね。。。
とりあえず、dockerでも自身のコンピュータのOS上でもMySQLなりpostgressなり動かす場合にはデータベースやらテーブルやらを作成する前にプロジェクトにあったもの(日本のプロジェクトの場合はだいたいutf8でしょうか)の初期設定を行ってはならないということですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Julia を始める人へ向けた MyWorkflow.jl の紹介

本日は

  • 数ヶ月ぶりにQiitaを更新します.
  • Julia アドベントカレンダー2020の記事です.これは jl.devもくもく会 の活動で作られました.

前置き

  • 任意のプログラミング言語を触るにあたって最初の壁になるのは環境構築だと思います.多くの人は Python, R との連携するために PyCall.jl, PyPlot.jl, SymPy.jl, Pandas.jl, RCall.jl といった言語間の連携をするパッケージ及び Jupyter Notebook/Lab の上で Julia を動かすためのパッケージとして IJulia.jl を導入することが多いでしょう.Twitterで #Julia言語 というハッシュタグを見るとつまづいている人が多く見られます.とりあえず,サクッと試したいのにそういうところでつまづいて成功体験を得られずに挫折しまう人いないでしょうか?

MyWorkflow.jl の紹介

  • MyWorkflow.jl ではそのような作業を済ませた Julia 動作環境を提供しています.厳密に言えば binder という Jupyter Notebook を動かすサービスに環境を作るスクリプトと Julia のサンプルコードを集めたGitHubのリポジトリです.

まずは動かしてみよう.

  • MyWorkflow.jlの先にあるREADMEを見ると launch binder というアイコンが見えます.

Binder

  • それをクリックするだけで Jupyter Notebook の上で Julia が動く環境ができます.ブラウザを閲覧できる環境であれば,PCに限らずスマホ,タブレット端末でも実行できます.アイコンをクリックするとした図のような画面が出ますのでしばらくお待ちください.

  • 動作環境は binder という Jupyter Notebook を動かすWebサービスを使いますので手元に Julia をインストールしなくても動作します.

image.png

しばらく待つと次のような画面がでます.Pythonなどで Jupyter Notebookを使ったことがある人には馴染みのある画面だと思います.

image.png

example.jl というのをクリック・タップしてみましょう.画面が移り変わり次の図のようになると思います.

image.png

パソコンでみている方は Shift キーと Enter を同時に押すことでセル(In []: で始まっているプログラムの塊)を実行することができます.スマホユーザーの場合は画面上にある Run というボタンを押すことで実行できます.動かした後は次のような画面になります.

image.png

やった! Hello, World できましたね.ここでは example.jl というファイルを開きましたが,末尾が .jl で終わるファイルならば同様に実行できるはずです.たとえば,

  • pyplot.jlPyPlot.jl という Python の matplotlib を Julia から呼び出す例を
  • pyjulia.jl は Python から Julia を呼び出す例

を提供しています.お時間がある時に触ってみてください.しばらく放置するとサーバーとの接続が切れます.また,改変したデータは残らないので,必要に応じてテキストをコピペして各自の手元に保存してください.

手元でも動かしてみたい.

上記の説明では binder という web サービスを使って Julia を走らせましたが,CPUリソースに一定の制限があります.手元のPCが充実しているのであればそちらで動作した方が良いかもしれません.

ひとまず https://github.com/terasakisatoshi/MyWorkflow.jl.git をクローンします. GitHub に慣れていればリポジトリをフォークするというのもありですね.

$ git clone https://github.com/terasakisatoshi/MyWorkflow.jl.git

Docker を使う場合

  • いきなり Docker かよ・・・.と思われるかもですが,この業界は怠け者が多いのでちょうど良いのではないでしょうか(この感想を持つ人はわかっている人だと思うので).Docker を使うと環境構築がローカル上に Julia をインストールしてパスを通して・・・というような作業も不要です.
$ make build

することで Docker を使って環境を構築することができます.これは Makefile を見るとわかりますが

$ rm -f Manifest.toml
$ docker build -t myworkflojl .
$ docker-compose build
$ docker-compose run --rm julia julia --project=/work -e 'using Pkg; Pkg.instantiate()'

とほぼ同じことをしています.このビルド作業はそれなりに時間がかかります.もっとサボりたい人は

$ make pull

をすると GitHubActions をトリガーにして作られた Docker のイメージが降ってきます.(ただし,リポジトリの master の物が降ってきますので特定のコミットの状態を利用する場合はmake build を用いてDockerイメージを作ってください.)

Tip

  • binder の環境は MyWorkflow.jl/binder/Dockerfile の Dockerfile を用いて構築される
  • 手元の環境は MyWorkflow.jl/Dockerfile を用いて作られる

コンテナを利用する

  • REPL を使う
$ docker-compose run --rm julia
Creating myworkflowjl_julia_run ... done
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.5.3 (2020-11-09)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> # ここで色々試す
  • Jupyter Notebook を使う場合
$ docker-compose up jupyter

この時

...
... 色々メッセージが出てくる
...
mylab      |     To access the notebook, open this file in a browser:
mylab      |         file:///root/.local/share/jupyter/runtime/nbserver-1-open.html
mylab      |     Or copy and paste one of these URLs:
mylab      |         http://19431004d7e8:8888/?token=xxxxxxxxxxxxxx
mylab      |      or http://127.0.0.1:8888/?token=xxxxxxxxxxxxx

というログが流れますので http://127.0.0.1:8888/?token=xxxxxxxxxxxxx をウェブブラウザURLの入力欄に貼り付けるとコンテナ内に作成されたカーネルにアクセスできます.

  • Jupyter Lab を使う場合
$ docker-compose up lab

のようにし上記の Jupyter Notebook の使う場合と同様の手順を踏んでください.

Julia ユーザーのためのノートブックである Pluto.jl を使う場合

$ docker-compose up pluto
Creating mypluto ... done
Attaching to mypluto
...
...色々メッセージが出てくる
...
mypluto    | Go to http://0.0.0.0:9999/?secret=xxxxxx in your browser to start writing ~ have fun!
mypluto    |
mypluto    | Press Ctrl+C in this terminal to stop Pluto
mypluto    |

Jupyter と同様に http://0.0.0.0:9999/?secret=xxxxxx をウェブブラウザのURL入力欄に貼り付けてください.

他にも Atom/Juno とコンテナを連携する機能も作っていますが,詳細は README.md をご覧ください.

Dockerを使いたくない人向け

  • 諸事情で使えない,使いたくない人向けに (Dockerに依存しない)Julia 環境構築を書いておきます.

Julia の環境構築(ダウンロードから)

  • https://julialang.org/downloads に行きます.Current stable release と書いてあるテーブルから自分の環境に合わせた物をダウンロードします.32bit ?64bit ?どっち? と迷われた場合は場合はとりあえず 64bit の方を選んでください.

image.png

  • 補足すると現時点の Long-term support (LTS) release1.0.5 ですが近々 1.6 が次期の LTS となることになっています.またJuliaのパッケージ(いわゆるライブラリ)は 1.3 以降で出てきた機能を使うことが少なくないので 2020/12月 現在ではstable release の 1.5.x を使うのが良いでしょう.軽微なバグフィックスされるごとに x の値が増えていきます(今は x=3 です)
  • 2020/12月現在は 1.6 は開発のステータス(DEV) になっています.もし 1.6 が安定版としてリリースされると 1.7 が開発のステータスになります.そして 1.5 はメンテナンスされなくなります.ですので,数ヶ月に一度の程度で定期的に Julia のホームページを見てリリース状態を確認するようにしてください.

インストール

  • Platform Specific Instructions for Official Binaries を見てください.Windowsユーザーの場合は rundll32 sysdm.cpl,EditEnvironmentVariables の存在を知ると幸せになるかもです.環境構築を設定するコントロールパネルのUIが一瞬で出てきます.
  • そして Path を通してターミナルで julia と入力してREPLがひらくことを確認しましょう.

MyWorkflow.jl の環境構築

ここでは MyWorkflow.jl を Julia のプロジェクトとしてみなし環境を構築することにします.今回紹介する MyWorkflow.jl だけでなく,自分でパッケージを開発したりするときにも役立つ tips が入っています.

$ git clone https://github.com/terasakisatoshi/MyWorkflow.jl
$ cd MyWorkflow.jl
$ julia --project=@. -e 'ENV["PYTHON"]=Sys.which("python3"); ENV["JUPYTER"]=Sys.which("jupyter"); using Pkg; Pkg.instantiate(); Pkg.precompile()'
...
... しばらく待つ
...

この後は jupyter notebook で Jupyter Notebook を立ち上げます.これで MyWorkflow.jl に依存する Julia パッケージを導入することができます.

何をやっているか書いておく

Pythonでは, venv, poetry や Anaconda の conda activate など,プロジェクト毎に依存するライブラリを整理する機能がありますが,Julia 同様に特定のプロジェクトに対して必要なパッケージを導入する機能を持っています.Juliaがそのプロジェクトにおいてどのようなパッケージ,どのバージョンをインストールするのかは Project.toml という toml フォーマットのファイルの情報に従います.

--project=@.

--project=Project.tomlのパス を指定することで Project.toml に対応する環境に入る準備ができます.指定しなければデフォルトの環境(npm などで言えば global なもの)としてみなされます.--project を指定せずに起動しても後から using Pkg; Pkg.activate(".") として切り替えることができます.今回の例では作業ディレクトリに Project.toml があるので --project=. でも良いです.--project=@. にすると作業ディレクトリに Project.toml がない場合は 親のディレクトリを遡り Project.toml を探しにいきます.

参考: https://stackoverflow.com/questions/53613663/what-is-in-julia-project-command-line-option

ちなみに export JULIA_PROJECT=@. のように環境変数を定義しておくと --project=@. の入力が省けます.

-e

-e '文字列' で文字列を Julia の式として評価します.-E もありますが違いは最後に評価した指揮の結果を出力するかしないかの違いです. たとえば julia -E '1+1' を試す.

 -e, --eval <expr>         Evaluate <expr>
 -E, --print <expr>        Evaluate <expr> and display the result

Windows 環境だと -e オプションが使えない場合があるようなのでパワーシェルをつかったり REPL を開いて実行するという方法で回避することもできます.

ENV["xxx"]=...

システムの環境変数を定義します.辞書データのように使うことができます.

ENV["PYTHON"]=Sys.which("python3") をすることで PYTHON という環境変数を python3 の実行形式が住んでいるフルパスを示してくれます.シェルでの which python3 のようなものです.これは PyCall.jl が Julia がよぶ Python どれなのかを明示的に指示するために使われます.もしローカルの環境に Python がない場合は ENV["PYTHON"] は空の文字列となります.その場合,Julia が ~/.julia/conda 以下に Miniconda を導入しこれと連携するようにできています.もしも,連携する Python を変更したい場合は

julia> ENV["PYTHON"]="新しいPythonのフルパス"
julia> using Pkg; Pkg.build("PyCall") # buildするのが大事

としてください.ここら辺の話は PyCall.jl のREADMEに全て書いてあります.

Windows の場合は path\\to\\something のようにパスの区切りを \\ にする必要があります.

Pkg.instantiate()

Project.toml の情報をパースして,依存パッケージの依存関係などを詳細に記述された Manifest.toml を作り依存関係の解決を図ります.ローカルの環境にパッケージがなかった場合は(そのパッケージが公式パッケージであれば)ダウンロードが始まります.一度 instantiate をした後は Manifest.toml の情報を読みにいきます.もしもおかしな挙動があった場合は一度 Manifest.toml 手動で消して改めて using Pkg; Pkg.instantiate() すると良いかもしれません.

Pkg.precompile()

Julia は JIT コンパイル方式で動作します.そのため using ... などで Julia パッケージをロードするとその度にコンパイルが走ります.そのオーバーヘッドを抑えるために Julia がコンパイル結果の一部をキャッシュするようになっています.

参考 https://docs.julialang.org/en/v1/manual/modules/#Module-initialization-and-precompilation

以上で ローカルでの環境構築の方法について述べました.色々手間があって大変ですね? Docker の方法を使うとコマンド一発でできますよ.(お?Dockerを使う気持ちになれましたか?)

まとめ

  • MyWorkflow.jl に入門する機会を用いて とりあえず既存の環境を用いて Julia を試してみたい人から,Docker を使って環境構築する方法,ローカルに環境を構築する方法まで幅広いユーザーに対して Julia の利用方法,環境構築の方法を提示しました.一年前以上からどうしたら Julia の環境をスムーズに構築できるか,気軽に使ってもらえるように考えた結果 Docker を用いた現在のような方法に至りました.自分の作業ワークフローは人それぞれなので各々の事情に合わせて適切はスタイルを身につけられるといいのではと思います.この記事がその助けになれば幸いです.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでCGI #1: EPWING電子辞書サーバー「let me see...」 (2003年)

はじめに

Docker始めました

流行ってから既に数年、今更Docker始めました。
導入手法が明確になり、環境も汚さないのは便利ですね。

使える環境(自宅サーバー)を手に入れたのでSSHでつないでやってます。
正直これで良いのでは、というかそもそもDockerである必要性は…。別に環境汚れても良いし。
まぁ本格的な開発をするならローカルPCでやった方が楽なはず。あと配布は気楽。

Let me see...

今回Docker対応したのは、電子辞書オープンラボ・かずひこ様による「let me see...」。
2000年代くらいまでCD-ROM電子辞書形式として使われたEPWING形式などに対応しています。
最終更新は2003年で、それをDocker対応するのがちょっと面白いですね。

GitHubレポジトリはkurema/forkedLetMeSee、Dockerイメージはghcr.io/kurema/letmeseeです。

image.png
image.png

導入方法

対応手順を説明する前に導入方法は以下です。
あらかじめEPWing形式の辞書を適当な場所に配置しておいてください。

$ git clone https://github.com/kurema/forkedLetMeSee.git
$ cd forkedLetMeSee
$ nano docker-compose.yml
$ nano conf/letmesee.conf
$ sudo docker-compose up -d

辞書の場所に応じdocker-compose.ymlを編集し、各辞書や設定をconf/letmesee.confに記述します。
conf/letmesee.confから見える場所はdocker-compose.ymlでのマウント先以下になるのは注意です。

開発過程

Dockerを初めて二つ目なのでやはり手探りです。
いくつかベストプラクティスではかもしれません。

Docker対応

Dockerってめんどくさいと思ってましたけど、ベースイメージからのインストール手順をRUNに書いていくだけの簡単な作業です。
Dockerfileが導入方法の説明にもなるので便利ですね。

タイムゾーン設定

最初にDockerを試した時、以下のメッセージが出ました。

Please select the geographic area in which you live. Subsequent configuration
questions will narrow this down by presenting a list of cities, representing
the time zones in which they are located.

Ubuntu 18.04以降でgitインストール時に発生するらしいです。
httpdでは必要ないかもしれませんが、tzdataは大抵必要だと思うので以下の対処をしています。
ただし、ユーザーが日本在住でない場合は別のタイムゾーンに設定する必要があります。

Dockerfile
RUN apt-get update -y && \
    apt-get install -y --no-install-recommends tzdata

#Timezone is set to Japan assuming you are in Japan.
ENV TZ=Asia/Tokyo

参考記事

眠れない夜 (2018)「[Docker] build tzdata タイムゾーン選択回避方法(ubuntu)」エンジニアの眠れない夜 https://sleepless-se.net/2018/07/31/docker-build-tzdata-ubuntu/
@yagince (2018) 「Docker Ubuntu18.04でtzdataをinstallするときにtimezoneの選択をしないでinstallする」Qiita https://qiita.com/yagince/items/deba267f789604643bab

CGI有効化

ApacheでCGIの有効化はsedを使って設定ファイルを弄るような必要があるようです。スマートではないですね。将来壊れそうです。
できれば専用コマンドが欲しいところ。

Dockerfile
RUN sed -ri 's/#LoadModule cgid_module/LoadModule cgid_module/g; \ 
             s/DirectoryIndex index.html/DirectoryIndex index.rb index.cgi index.html/g; \ 
             s/Options Indexes FollowSymLinks/Options Indexes FollowSymLinks ExecCGI/g; \
             s/#Scriptsock cgisock/Scriptsock cgisock/g; \
             s/#AddHandler cgi-script .cgi/AddHandler cgi-script .pl .rb .cgi/g' /usr/local/apache2/conf/httpd.conf

ベースコンテナによってhttpd.confの場所が変わります。
扱っているCGIによってAddHandler cgi-scriptDirectoryIndexの拡張子が変わったりします。今回は.rb

Scriptsock cgisockをコメントインしないとService Unavailableのエラーが出るようです(参考)。出ました。
以前やった記憶がなかったですが、その時はubuntuでa2enmodが使えたのでそのときに導入されるようです。

mpm_prefork_moduleを使う可能性がある場合はs/#LoadModule cgi_module/LoadModule cgi_module/g;も追加した方が良いでしょう。cgidcgiになります。

参考記事

ワタナベ書店 (2015) 「Docker上でApacheコンテナを作成しCGIのコンテンツを走らせるまで」 https://senyoltw.hatenablog.jp/entry/2015/10/21/175847

配置

後はファイルを配置するだけ。
CGIでは実行ファイルにchmodで実行権限を付与すること忘れないようにしましょう。今回は777にしました。

個人的にはREADME.mdファイルをイメージに配置するのが良いと思います。
何かの理由でDockerイメージだけあるという状況には多少便利です。
レポジトリ全体を圧縮して配置するとかも良いですが、大きめの画像を配置したりすることもあるのでやめました。
容量を気にするなら辞めるなりgz圧縮なり。

Dockerfile
COPY edict-devel/letmesee/ /usr/local/apache2/htdocs/
RUN chmod 777 /usr/local/apache2/htdocs/*.rb

COPY README.md /

トラブル

当初、gem installをする段でSSLエラーが出るというトラブルがありました。
一時期証明書の追加で対処しましたが、CGI側の修正で不要になりました。
それについてはこちらの記事参照。

docker-compose

このCGIは設定ファイルがCGIと同じフォルダに配置されるタイプです。
Dockerイメージ内部のファイル自体を触るのは微妙なのでvolumesでマウントします。
辞書自体のマウントも必要です。
dockerコマンド一行にしては長いですし、docker-composeファイルにしました。

docker-compose.yml
version: '3'
services:

  letmesee:
    image: ghcr.io/kurema/letmesee:latest
    container_name: letmesee
    restart: always
    ports: 
      - 50002:80
    volumes:
      - ./conf/letmesee.conf:/usr/local/apache2/htdocs/letmesee.conf
      # /home/share/DictionaryをEPWing辞書が保存されている場所に変更してください。
      - /home/share/Dictionary:/usr/local/dict

公開

Docker HubがPull回数制限や利用されていないイメージの削除(後に保留)などを発表したので、イメージはGitHub Container Registryに保存することにしました。
6ヶ月どころか十年単位で放置する見込みなので、継続性が期待できるGitHubのサービスが確実だと判断しました。
マルチCPUアーキテクチャにも対応していて便利です。なにより無料。

GitHub Actions

GitHub Actionsで自動化をしたかったのでGitHubコミュニティーの例のマルチCPUアーキテクチャ対応のワークフローをほぼそのまま使いました。
以下の点を変更しています。

  • キャッシュはインラインで保存。GitHub Actions側に保存する方法もあるようです。
.github/workflows/docker.yml
          cache-from: type=registry,ref=${{ steps.prep.outputs.latest }}
          cache-to: type=inline
  • テスト用手動ディスパッチ。手動時のタグはtestになります。
.github/workflows/docker.yml
on:
  workflow_dispatch:
  • デイリービルドは頻度が高すぎるので月刊に。
  • プラットフォームは元イメージと同じ。無料だからってやりすぎですね。
.github/workflows/docker.yml
          platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x,linux/386,linux/arm/v7

最終的なyamlはこちら(記事時点)参照。

GitHub Container Registryはベータテスト中なのでGITHUB_TOKENが使えない点は注意が必要です。
自分でトークンを発行し、GHCR_PATをシークレットに登録してください。

時間

ビルド時間はキャッシュなしで11分。キャッシュありで2分といったところです(testタグはキャッシュ元として参照しません)。
image.png

タグを付けたら自動でイメージをビルドしプッシュしてくれるのは楽です。
マンスリービルドを参照すれば最新のベースコンテナにも追従できますし。
ただ実を言えば個人開発でCI/CDなんてのは建前で、以下みたいな目的が本音ですね。

  • 無料なので使いたい
  • 重い処理をオフロードしたい
  • ミスって個人情報が混入する事態を避けたい。

なので、テスト目的でもガンガンGitHub Actionsで実行して、GitHub Container Registryからイメージをpullして試してます。自前ビルドはしません。
幸いキャッシュが効いてるとCGIだけの修正では処理時間も短くサーバーの容量も新規では大して食いません。
合わせても100kb未満のCGIそれもダウンロード数が1-2桁(現時点では自分だけ)に数百MBを消費するのはどうなのかと最初は思いましたし、z/Architectureなんて環境でこれを使う人なんて絶対居ませんが、Dockerってのはそういう世界みたいです。怖いですね。

改修

流石に古いだけあってそのまますんなり動くわけではありませんでしたが、少しの修正で動きます。
コメントがなくrubyは滅多に書かなくても分かりやすかったです。
元作者さんとruby開発者は見事です。

  • Ruby側の更新に追従
    • 相対パスへのrequirerequire_relativeに。
      • 2003年当時はrequire 'letmesee.rb'で同一パスのrbファイルを参照してくれたようです。
      • 現時点ではrequire './letmesee.rb'にすれば動作します。
      • 実際にはrequire_relative './letmesee.rb'が正しいようです。これを書いている途中に気付いたので修正しました。
      • CGIならカレントディレクトリの問題とかは起きないはずですが、念のため。
    • File::open( path )File::open( path , "r:utf-8" )に修正。
      • 記憶にありませんが、文字化けでもしたんだと思います。
    • head['Content-Length'] = body.size.to_shead['Content-Length'] = body.bytesize.to_sに修正。
      • 2003年当時はstring.sizeでバイト数を取得できたようです。上の修正のせいかもしれません。
      • 出力が途中で途切れていたので不思議でした。
    • Iconv.iconvの利用をやめてEncoding::Converter#convertを利用するようにしました。
      • iconvの引数はiconv(to, from, *strs)なのに対し、Encodingsではnew(source_encoding, destination_encoding)と文字コードの順番が逆なので注意が必要です。
      • iconvのUTF-8はUTF8でも通るようですが、Encodingsでは通らないので注意が必要です(元々のバグかも)。
      • Encodingsでは変換元と変換後が同じ文字コードだとエラーが発生するようです。素通りしてくれれば良いのに。
  • HTML5対応
    • 音声には<audio>を使うようにしました。
      • 動画はMPEG-1なので<video>ではまず動きません。
        • JavaScriptで再生するjsmpegやwasm版のffmpegとかもありますが、十年単位で放置する予定なので辞めました。
    • ヘッダの変更。簡単なスマートフォン対応(cssで@mediaの追加・viewport)。
old
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
        "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
<%= css_tag %>
new
<!DOCTYPE html>

<html lang="ja">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
<%= css_tag %>
  • サーバーの性能向上に伴い、ちょっと富豪的に。
    • 辞書を絞って検索するメニューを削除。
      • 邪魔なので。<details>でもいいと思いますが、当時(2017年)は知りませんでした。
    • 外字をデフォルトで最大フォントサイズに。
      • 高解像度モニターも増えてますし。
      • 存在しない場合最小フォントサイズの外字にフォールバックします。
  • 高速化。
    • トップページの静的表示
      • letmesee.rb初期化時全辞書を読み込んでいたので、低性能マシンかつ大量辞書の環境では表示に時間が掛かっていました。
      • トップページでは辞書を読み込まず静的ファイルを表示するようにしたので表示が早くなりました。
    • 外字のキャッシュ
      • 外字が大量にある場合に遅かったので、ヘッダを変更し一日はキャッシュするようにしました。
      • 本来は一文字ずつ取得しに行く処理は避けるべきです(後述)。
  • XML対応
    • XMLでの取得に対応しました。ただし、常にwell-formedだとは期待できません。
    • Androidアプリでも作ろうかと思いましたが辞めました。
  • <b><i>
    • <b><i>が入れ子になっても正常動作するように修正しました。
      • 市販されている辞書では存在しないのか、入れ子に対応していないEPWINGソフトは結構あります。
      • 変換された辞書では普通にあって、検索候補の途中から盛大にレイアウトが崩れるので面倒です。
    • 対応しないタグを削除するようにしました。
  • その他
    • CSSや設定などを個人的な好みに合わせて変更。
    • 独自テーマを追加し、デフォルトをそちらに変更。
    • ロゴ画像を透過。

改修しなかった点は以下です。

  • 設定ファイルでの辞書記述
    • 大量の辞書を保存している場合、設定ファイルに一つずつ辞書のパスを追加するのは面倒です。
    • 自動追加するスクリプトを書くか、最上位パスの指定だけで済むように変更したかったですが面倒なので辞めました。
  • 外字表示の高速化
    • 現状では外字一文字ずつサーバーに問い合わせるようになっています。
    • 外字が多いと毎回CGIが起動して大量のIO処理が発生し時間が掛かります。
      • キャッシュはするようにしました。
    • 辞書ファイル側では各サイズ2ファイルで小容量なので一括で渡した方が良いかもしれません。
      • 普通は数kb程度ですが、大きいと2MB程度にはなります。
      • これはかなり大変そうでした。辞めました。
  • さらなるデザインの改良。
    • 自分で新しいデザインにしましたが、今見るとダサいです。
    • これは今後修正するかもしれません。

手間を掛けず、簡単にできる修正だけしたという感じです。
ruby自体まず書かないですが、読みやすく破壊的変更も大してないので楽でした。
またこのCGI自体テーマがあったり拡張しやすい設計です。素晴らしい
CGIは良いものですね。シンプルで分かりやすくて。

年表

日時 イベント
1987年 EPWINGの前身となるWINGフォーマット制定。
1988年 EPWING規約制定。
1993年 CGI登場
1995年 Ruby登場
1997年6月8日 EB Library 初版
2000年4月10日 ruby EB v1.0
2002年3月31日 ruby EB v1.7 (最新版)
2003年2月29日 let me see... v1.0
2003年11月9日 let me see... v1.1 (最新版)
2010年3月8日 EB Library v4.4.3 (最新版)
2013年3月13日 Docker 初版
2014年1月30日 ruby EB 久保健洋氏によるruby1.9対応非公式フォーク 初版
2014年1月30日 ruby EB 同フォーク 最終更新
2017年7月11日 let me see... kuremaによるフォーク v1.2 (微修正)
2020年11月10日 let me see... 同フォーク v1.3 (Docker対応)

しかしすごい歴史ですね。壮大。
まぁEPWINGの世界ってのはこんな感じです。Unicode対応以外は現在でもそれなりに「使える」フォーマットなのは感心します。
違う世代の辞書ファンたちがちょっとずつ色々何かやってるのがEPWINGの魅力です。
今でもずっと辞書アプリを開発してくださってる方もいらっしゃいます。感謝。

ちなみにこの記事では細かく触れませんでしたが、2017年にフォークし小改良、最近Docker対応のついでに中改良といった感じです。
さらに言えば記事の順番が前後しますが、これは私のDockerでCGI二つ目です。
一つ目はニコニコチャンネルキャッシュサーバー。そのうち記事にします。

その他、EPWING関係リンク:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでCGI: EPWING電子辞書サーバー「let me see...」 (2003年)

はじめに

Docker始めました

流行ってから既に数年、今更Docker始めました。
導入手法が明確になり、環境も汚さないのは便利ですね。

使える環境(自宅サーバー)を手に入れたのでSSHでつないでやってます。
正直これで良いのでは、というかそもそもDockerである必要性は…。別に環境汚れても良いし。
まぁ本格的な開発をするならローカルPCでやった方が楽なはず。あと配布は気楽。

Let me see...

今回Docker対応したのは、電子辞書オープンラボ・かずひこ様による「let me see...」。
2000年代くらいまでCD-ROM電子辞書形式として使われたEPWING形式などに対応しています。
最終更新は2003年で、それをDocker対応するのがちょっと面白いですね。

GitHubレポジトリはkurema/forkedLetMeSee、Dockerイメージはghcr.io/kurema/letmeseeです。

image.png
image.png

導入方法

対応手順を説明する前に導入方法は以下です。
あらかじめEPWing形式の辞書を適当な場所に配置しておいてください。

$ git clone https://github.com/kurema/forkedLetMeSee.git
$ cd forkedLetMeSee
$ nano docker-compose.yml
$ nano conf/letmesee.conf
$ sudo docker-compose up -d

辞書の場所に応じdocker-compose.ymlを編集し、各辞書や設定をconf/letmesee.confに記述します。
conf/letmesee.confから見える場所はdocker-compose.ymlでのマウント先以下になるのは注意です。

開発過程

Dockerを初めて二つ目なのでやはり手探りです。
いくつかベストプラクティスではかもしれません。

Docker対応

Dockerってめんどくさいと思ってましたけど、ベースイメージからのインストール手順をRUNに書いていくだけの簡単な作業です。
Dockerfileが導入方法の説明にもなるので便利ですね。

タイムゾーン設定

最初にDockerを試した時、以下のメッセージが出ました。

Please select the geographic area in which you live. Subsequent configuration
questions will narrow this down by presenting a list of cities, representing
the time zones in which they are located.

Ubuntu 18.04以降でgitインストール時に発生するらしいです。
httpdでは必要ないかもしれませんが、tzdataは大抵必要だと思うので以下の対処をしています。
ただし、ユーザーが日本在住でない場合は別のタイムゾーンに設定する必要があります。

Dockerfile
RUN apt-get update -y && \
    apt-get install -y --no-install-recommends tzdata

#Timezone is set to Japan assuming you are in Japan.
ENV TZ=Asia/Tokyo

参考記事

眠れない夜 (2018)「[Docker] build tzdata タイムゾーン選択回避方法(ubuntu)」エンジニアの眠れない夜 https://sleepless-se.net/2018/07/31/docker-build-tzdata-ubuntu/
@yagince (2018) 「Docker Ubuntu18.04でtzdataをinstallするときにtimezoneの選択をしないでinstallする」Qiita https://qiita.com/yagince/items/deba267f789604643bab

CGI有効化

ApacheでCGIの有効化はsedを使って設定ファイルを弄るような必要があるようです。スマートではないですね。将来壊れそうです。
できれば専用コマンドが欲しいところ。

Dockerfile
RUN sed -ri 's/#LoadModule cgid_module/LoadModule cgid_module/g; \ 
             s/DirectoryIndex index.html/DirectoryIndex index.rb index.cgi index.html/g; \ 
             s/Options Indexes FollowSymLinks/Options Indexes FollowSymLinks ExecCGI/g; \
             s/#Scriptsock cgisock/Scriptsock cgisock/g; \
             s/#AddHandler cgi-script .cgi/AddHandler cgi-script .pl .rb .cgi/g' /usr/local/apache2/conf/httpd.conf

ベースコンテナによってhttpd.confの場所が変わります。
扱っているCGIによってAddHandler cgi-scriptDirectoryIndexの拡張子が変わったりします。今回は.rb

Scriptsock cgisockをコメントインしないとService Unavailableのエラーが出るようです(参考)。出ました。
以前やった記憶がなかったですが、その時はubuntuでa2enmodが使えたのでそのときに導入されるようです。

mpm_prefork_moduleを使う可能性がある場合はs/#LoadModule cgi_module/LoadModule cgi_module/g;も追加した方が良いでしょう。cgidcgiになります。

参考記事

ワタナベ書店 (2015) 「Docker上でApacheコンテナを作成しCGIのコンテンツを走らせるまで」 https://senyoltw.hatenablog.jp/entry/2015/10/21/175847

配置

後はファイルを配置するだけ。
CGIでは実行ファイルにchmodで実行権限を付与すること忘れないようにしましょう。今回は777にしました。

個人的にはREADME.mdファイルをイメージに配置するのが良いと思います。
何かの理由でDockerイメージだけあるという状況には多少便利です。
レポジトリ全体を圧縮して配置するとかも良いですが、大きめの画像を配置したりすることもあるのでやめました。
容量を気にするなら辞めるなりgz圧縮なり。

Dockerfile
COPY edict-devel/letmesee/ /usr/local/apache2/htdocs/
RUN chmod 777 /usr/local/apache2/htdocs/*.rb

COPY README.md /

トラブル

当初、gem installをする段でSSLエラーが出るというトラブルがありました。
一時期証明書の追加で対処しましたが、CGI側の修正で不要になりました。
それについてはこちらの記事参照。

docker-compose

このCGIは設定ファイルがCGIと同じフォルダに配置されるタイプです。
Dockerイメージ内部のファイル自体を触るのは微妙なのでvolumesでマウントします。
辞書自体のマウントも必要です。
dockerコマンド一行にしては長いですし、docker-composeファイルにしました。

docker-compose.yml
version: '3'
services:

  letmesee:
    image: ghcr.io/kurema/letmesee:latest
    container_name: letmesee
    restart: always
    ports: 
      - 50002:80
    volumes:
      - ./conf/letmesee.conf:/usr/local/apache2/htdocs/letmesee.conf
      # /home/share/DictionaryをEPWing辞書が保存されている場所に変更してください。
      - /home/share/Dictionary:/usr/local/dict

公開

Docker HubがPull回数制限や利用されていないイメージの削除(後に保留)などを発表したので、イメージはGitHub Container Registryに保存することにしました。
6ヶ月どころか十年単位で放置する見込みなので、継続性が期待できるGitHubのサービスが確実だと判断しました。
マルチCPUアーキテクチャにも対応していて便利です。なにより無料。

GitHub Actions

GitHub Actionsで自動化をしたかったのでGitHubコミュニティーの例のマルチCPUアーキテクチャ対応のワークフローをほぼそのまま使いました。
以下の点を変更しています。

  • キャッシュはインラインで保存。GitHub Actions側に保存する方法もあるようです。
.github/workflows/docker.yml
          cache-from: type=registry,ref=${{ steps.prep.outputs.latest }}
          cache-to: type=inline
  • テスト用手動ディスパッチ。手動時のタグはtestになります。
.github/workflows/docker.yml
on:
  workflow_dispatch:
  • デイリービルドは頻度が高すぎるので月刊に。
  • プラットフォームは元イメージと同じ。無料だからってやりすぎですね。
.github/workflows/docker.yml
          platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x,linux/386,linux/arm/v7

最終的なyamlはこちら(記事時点)参照。

GitHub Container Registryはベータテスト中なのでGITHUB_TOKENが使えない点は注意が必要です。
自分でトークンを発行し、GHCR_PATをシークレットに登録してください。

時間

ビルド時間はキャッシュなしで11分。キャッシュありで2分といったところです(testタグはキャッシュ元として参照しません)。
image.png

タグを付けたら自動でイメージをビルドしプッシュしてくれるのは楽です。
マンスリービルドを参照すれば最新のベースコンテナにも追従できますし。
ただ実を言えば個人開発でCI/CDなんてのは建前で、以下みたいな目的が本音ですね。

  • 無料なので使いたい
  • 重い処理をオフロードしたい
  • ミスって個人情報が混入する事態を避けたい。

なので、テスト目的でもガンガンGitHub Actionsで実行して、GitHub Container Registryからイメージをpullして試してます。自前ビルドはしません。
幸いキャッシュが効いてるとCGIだけの修正では処理時間も短くサーバーの容量も新規では大して食いません。
合わせても100kb未満のCGIそれもダウンロード数が1-2桁(現時点では自分だけ)に数百MBを消費するのはどうなのかと最初は思いましたし、z/Architectureなんて環境でこれを使う人なんて絶対居ませんが、Dockerってのはそういう世界みたいです。怖いですね。

改修

流石に古いだけあってそのまますんなり動くわけではありませんでしたが、少しの修正で動きます。
コメントがなくrubyは滅多に書かなくても分かりやすかったです。
元作者さんとruby開発者は見事です。

  • Ruby側の更新に追従
    • 相対パスへのrequirerequire_relativeに。
      • 2003年当時はrequire 'letmesee.rb'で同一パスのrbファイルを参照してくれたようです。
      • 現時点ではrequire './letmesee.rb'にすれば動作します。
      • 実際にはrequire_relative './letmesee.rb'が正しいようです。これを書いている途中に気付いたので修正しました。
      • CGIならカレントディレクトリの問題とかは起きないはずですが、念のため。
    • File::open( path )File::open( path , "r:utf-8" )に修正。
      • 記憶にありませんが、文字化けでもしたんだと思います。
    • head['Content-Length'] = body.size.to_shead['Content-Length'] = body.bytesize.to_sに修正。
      • 2003年当時はstring.sizeでバイト数を取得できたようです。上の修正のせいかもしれません。
      • 出力が途中で途切れていたので不思議でした。
    • Iconv.iconvの利用をやめてEncoding::Converter#convertを利用するようにしました。
      • iconvの引数はiconv(to, from, *strs)なのに対し、Encodingsではnew(source_encoding, destination_encoding)と文字コードの順番が逆なので注意が必要です。
      • iconvのUTF-8はUTF8でも通るようですが、Encodingsでは通らないので注意が必要です(元々のバグかも)。
      • Encodingsでは変換元と変換後が同じ文字コードだとエラーが発生するようです。素通りしてくれれば良いのに。
  • HTML5対応
    • 音声には<audio>を使うようにしました。
      • 動画はMPEG-1なので<video>ではまず動きません。
        • JavaScriptで再生するjsmpegやwasm版のffmpegとかもありますが、十年単位で放置する予定なので辞めました。
    • ヘッダの変更。簡単なスマートフォン対応(cssで@mediaの追加・viewport)。
old
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
        "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
<%= css_tag %>
new
<!DOCTYPE html>

<html lang="ja">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
<%= css_tag %>
  • サーバーの性能向上に伴い、ちょっと富豪的に。
    • 辞書を絞って検索するメニューを削除。
      • 邪魔なので。<details>でもいいと思いますが、当時(2017年)は知りませんでした。
    • 外字をデフォルトで最大フォントサイズに。
      • 高解像度モニターも増えてますし。
      • 存在しない場合最小フォントサイズの外字にフォールバックします。
  • 高速化。
    • トップページの静的表示
      • letmesee.rb初期化時全辞書を読み込んでいたので、低性能マシンかつ大量辞書の環境では表示に時間が掛かっていました。
      • トップページでは辞書を読み込まず静的ファイルを表示するようにしたので表示が早くなりました。
    • 外字のキャッシュ
      • 外字が大量にある場合に遅かったので、ヘッダを変更し一日はキャッシュするようにしました。
      • 本来は一文字ずつ取得しに行く処理は避けるべきです(後述)。
  • XML対応
    • XMLでの取得に対応しました。ただし、常にwell-formedだとは期待できません。
    • Androidアプリでも作ろうかと思いましたが辞めました。
  • <b><i>
    • <b><i>が入れ子になっても正常動作するように修正しました。
      • 市販されている辞書では存在しないのか、入れ子に対応していないEPWINGソフトは結構あります。
      • 変換された辞書では普通にあって、検索候補の途中から盛大にレイアウトが崩れるので面倒です。
    • 対応しないタグを削除するようにしました。
  • その他
    • CSSや設定などを個人的な好みに合わせて変更。
    • 独自テーマを追加し、デフォルトをそちらに変更。
    • ロゴ画像を透過。

改修しなかった点は以下です。

  • 設定ファイルでの辞書記述
    • 大量の辞書を保存している場合、設定ファイルに一つずつ辞書のパスを追加するのは面倒です。
    • 自動追加するスクリプトを書くか、最上位パスの指定だけで済むように変更したかったですが面倒なので辞めました。
  • 外字表示の高速化
    • 現状では外字一文字ずつサーバーに問い合わせるようになっています。
    • 外字が多いと毎回CGIが起動して大量のIO処理が発生し時間が掛かります。
      • キャッシュはするようにしました。
    • 辞書ファイル側では各サイズ2ファイルで小容量なので一括で渡した方が良いかもしれません。
      • 普通は数kb程度ですが、大きいと2MB程度にはなります。
      • これはかなり大変そうでした。辞めました。
  • さらなるデザインの改良。
    • 自分で新しいデザインにしましたが、今見るとダサいです。
    • これは今後修正するかもしれません。

手間を掛けず、簡単にできる修正だけしたという感じです。
ruby自体まず書かないですが、読みやすく破壊的変更も大してないので楽でした。
またこのCGI自体テーマがあったり拡張しやすい設計です。素晴らしい
CGIは良いものですね。シンプルで分かりやすくて。

年表

日時 イベント
1987年 EPWINGの前身となるWINGフォーマット制定。
1988年 EPWING規約制定。
1993年 CGI登場
1995年 Ruby登場
1997年6月8日 EB Library 初版
2000年4月10日 ruby EB v1.0
2002年3月31日 ruby EB v1.7 (最新版)
2003年2月29日 let me see... v1.0
2003年11月9日 let me see... v1.1 (最新版)
2010年3月8日 EB Library v4.4.3 (最新版)
2013年3月13日 Docker 初版
2014年1月30日 ruby EB 久保健洋氏によるruby1.9対応非公式フォーク 初版
2014年1月30日 ruby EB 同フォーク 最終更新
2017年7月11日 let me see... kuremaによるフォーク v1.2 (微修正)
2020年11月10日 let me see... 同フォーク v1.3 (Docker対応)

しかしすごい歴史ですね。壮大。
まぁEPWINGの世界ってのはこんな感じです。Unicode対応以外は現在でもそれなりに「使える」フォーマットなのは感心します。
違う世代の辞書ファンたちがちょっとずつ色々何かやってるのがEPWINGの魅力です。
今でもずっと辞書アプリを開発してくださってる方もいらっしゃいます。感謝。

ちなみにこの記事では細かく触れませんでしたが、2017年にフォークし小改良、最近Docker対応のついでに中改良といった感じです。
さらに言えば記事の順番が前後しますが、これは私のDockerでCGI二つ目です。
一つ目はニコニコチャンネルキャッシュサーバー。そのうち記事にします。

その他、EPWING関係リンク:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nodejsのコンテナを作成してみた。

はじめに

Nodejsのバックエンドサーバーをコンテナで作成する際、ベースイメージをnodejs(node:10) にしてDockerfileを作成したが、コンテナにアタッチができず、中の構成などを直接見る方法が分からなかった。
困ったときにはnodejsの設定などを見れるようにしたかったので、試しにUbuntuベースにnodejsをインストールする方法でDockerfileを作成してみた。
(記事の内容は、試しに動かすところまで記載あり。)

実行環境(前提)

【Docker導入環境】
  ・Ubuntu 20.04 LTS(GCP上)
  ・docker 19.03.13

今回やった事のメモ

今回の作業のためにテストフォルダを作成して、そこに移動。

$ sudo mkdir ./test_container
$ cd ./test_container

Dockerfileの作成

$ sudo nano ./Dockerfile
Dockerfile
# ベースイメージ
FROM ubuntu:20.04

# 必要パッケージのインストール
RUN apt update
RUN apt install -y tzdata
RUN apt install -y \
  nodejs \
  npm

# Nodejs関連のパッケージインストール
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
COPY ./package.json ./
RUN npm install

# index.jsファイルの設置
COPY ./index.js ./

# ポート開放
EXPOSE 8080

CMD ["node", "index.js"]

nodejsモジュールをインストールするためのpackage.jsonを作成
※上記のnpm installのタイミングで、package.jsonの情報を元にインストールが行われる。

$ sudo nano ./package.json
package.json
{
  "name": "test",
  "description": "test",
  "version": "0.0.1",
  "main": "index.js",
  "private": true,
  "license": "Apache Version 2.0",
  "author": "Google Inc.",
  "engines": {
    "node": "10"
  },

  "dependencies": {
    "express": "^4.17.1",
    "moment-timezone": "^0.5.31",
    "body-parser": "^1.19.0"
  }
}

index.jsを作成

$ sudo nano ./index.js
index.js
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 8080;


app.use(bodyParser.urlencoded({ extended: true }));

app.get('/get_test1', function(req, res) {
  res.send('GET1パラメータ取得: ' + req.query.get1)
});

app.get('/get_test2', function(req, res) {
  res.send('GET2パラメータ取得: ' + req.query.get2)
});

app.post('/post_test1', function(req, res) {
  res.send('POST-URLへの送信です。')
});

app.post('/post_test2', function(req, res) {
  res.send('POSTパラメータ取得: ' + req.body.data1)
});

app.listen(port)

ビルド実行

$ docker image build -t test_container:v1 ./

コンテナ作成 & 起動

$ docker container run -it -d -p 80:8080 --name con1 test_container:v1

動作確認

GETの動きを確認
以下に接続して、上記で設定したレスポンスが返ってくればOK。

 外部IP/get_test1?get1=10
 外部IP/get_test2?get2=20

POSTの動きを確認
ローカル環境(自分のPC)に適当なhtmlファイルを作成して、それをブラウザで開きアクセス。

post_test1.html(POST送信のテスト用)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>POSTテスト用</title>
</head>

<body>

<form action="http://[外部IP]/post_tset" method="post">
  <input type="hidden" name="data1" value="aiueo">
  <input type="submit" value="送信">
</form>

</body>
</html>
post_test2.html(POSTパラメータ取得の確認用)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>POSTテスト用</title>
</head>

<body>

<form action="http://[外部IP]/post_tset2" method="post">
  <input type="hidden" name="data1" value="aiueo">
  <input type="submit" value="送信">
</form>

</body>
</html>

おまけ

はじめの背景の部分で 『コンテナにアタッチができず、中の構成などを直接見る方法が分からなかった』 と書いたが、コンテナ作成時の[CMD] コマンドを /bin/bash で上書きできていなかっただけだった・・・

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker のコンテナを一括で削除したい時

はじめに

最近Dockerを使った開発に挑戦していて、試行錯誤の結果大量にコンテナが出来てしまっていたので一括削除の方法を調べた。

結論

下記のコマンドを叩く。

docker rm $(docker ps -aq)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker のコンテナを全て掃除したい時

はじめに

最近Dockerを使った開発に挑戦していて、試行錯誤の結果大量にコンテナが出来てしまっていたので一括削除の方法を調べた。

結論

下記のコマンドを叩く。

docker rm $(docker ps -aq)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerfileでビルド時に外部から変数を注入して、ファイルに書き込みたい

目的

  • Dockerfileでビルド時に外部から変数を注入して、ファイルに書き込みたい

ARGとENVを用いる

Dockerfile
FROM alpine
ARG TEST
ENV TEST ${TEST}
RUN echo "TEST eq ${TEST}" > /test

docker-compose.yml
version: "3"

services:
  testweb:
    build: 
      context: .
      dockerfile: ./Dockerfile
      args:
        - TEST="aaa"
    tty: true
$ docker-compose up --build -d && docker ps
Building testweb
Step 1/4 : FROM alpine
 ---> d6e46aa2470d
Step 2/4 : ARG TEST
 ---> Using cache
 ---> 7a550663ef1a
Step 3/4 : ENV TEST ${TEST}
 ---> Using cache
 ---> 63c05c3ea630
Step 4/4 : RUN echo "TEST eq ${TEST}" > /test
 ---> Using cache
 ---> 8e0906c4cb26

Successfully built 8e0906c4cb26
Successfully tagged test2_testweb:latest
Starting test2_testweb_1 ... done
CONTAINER ID   IMAGE           COMMAND     CREATED          STATUS                  PORTS     NAMES
e4726f3e8e02   test2_testweb   "/bin/sh"   11 seconds ago   Up Less than a second             test2_testweb_1

$ docker exec -it e4 sh
/ # cat /test
TEST eq aaa

Ref

Dockerfile リファレンス — Docker-docs-ja 1.11.0 ドキュメント

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nextjs-auth0をdocker上でも使いたい

nextjsでも簡単にauth0を使いたい!
と思ってサンプルに従うと、Dockerビルドってかproduction buildするとエラーで落ちちゃいます。

configのサンプル
auth0/nextjs-auth0: Next.js SDK for signing in with Auth0 (Experimental) - https://github.com/auth0/nextjs-auth0#runtime-configuration

import { initAuth0 } from '@auth0/nextjs-auth0';
import config from './config';

export default initAuth0({
  domain: process.env.AUTHO_DOMAIN,
  clientId: process.env.AUTHO_DOMAIN,
  clientSecret: process.env.APP_SECRRET,
  scope: 'openid profile',

エラー内容こんな感じ。process.env.XXXが見れないから落ちてるのかな。

Automatically optimizing pages...
> Build error occurred
Error: A valid Auth0 Domain must be provided
    at Object.createInstance [as default] (/usr/src/app/node_modules/@auth0/nextjs-auth0/dist/instance.node.js:10:15)
    at initAuth0 (/usr/src/app/node_modules/@auth0/nextjs-auth0/dist/index.js:8:46)
    at Object.xMDF (/usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:781:127)
    at __webpack_require__ (/usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:23:31)
    at Module.1TCz (/usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:147:13)
    at __webpack_require__ (/usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:23:31)
    at Object.0 (/usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:99:18)
    at __webpack_require__ (/usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:23:31)
    at /usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:91:18
    at Object.<anonymous> (/usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:94:10)
error Command failed with exit code 1

ボツ案

issue投げて聞いてみたら、 .env を仮データで追加し実行時に再ビルドすると大丈夫らしい。試したらできた。

Can't build on docker. · Issue #86 · auth0/nextjs-auth0 - https://github.com/auth0/nextjs-auth0/issues/86

Dockerfile
FROM node:14 as builder

WORKDIR /app

COPY .env.template .env
COPY package.json yarn.lock ./
RUN yarn install

COPY . .
RUN yarn build

# ------------------
FROM node:14-alpine as release

WORKDIR /app

COPY --from=builder /app/package.json /app/yarn.lock ./
RUN yarn install
COPY --from=builder /app/.next ./.next
COPY . .

EXPOSE 3000
CMD ["yarn", "start:build"]

package.json
  "scripts": {
    "start": "next start",
    "start:build": "yarn build && yarn start",
    "build": "next build",

確かにできるんだけど、CI時にビルド2回ぐらい回しちゃうことになって、めっちゃ時間かかる・・・

改善案

initAuth0のオブジェクトを直接export defaultすると、環境変数がバインドされちゃうので、関数にしてあとからprocess.envを評価すればいいじゃんって案

Document best practices for using nextjs-auth0 with a nextjs production build. · Issue #154 · auth0/nextjs-auth0 - https://github.com/auth0/nextjs-auth0/issues/154

環境変数はgetConfig()経由で受け取り、バインドすると想定通り動いてくれた。やったね!

src/lib/auth0.js
import getConfig from 'next/config';
import { initAuth0 } from '@auth0/nextjs-auth0';

const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();

let auth0 = null;

const proc = () => {
  if (!auth0) {
    auth0 = initAuth0({
      clientId: publicRuntimeConfig.auth0.clientId,
      domain: publicRuntimeConfig.auth0.domain,
      scope: 'openid email profile',
      postLogoutRedirectUri: publicRuntimeConfig.baseUrl,
      redirectUri: `${publicRuntimeConfig.baseUrl}/api/auth/signed-in`,
      clientSecret: serverRuntimeConfig?.auth0?.secret ?? '',
      session: {
        cookieSecret: serverRuntimeConfig?.appSecret ?? '',
        // Set to 8 hours
        cookieLifetime: 60 * 60 * 8,
        storeIdToken: false,
        storeAccessToken: false,
        storeRefreshToken: false,
      },
      oidcClient: {
        // Optionally configure the timeout in milliseconds for HTTP requests to Auth0.
        httpTimeout: 2500,
        // Optionally configure the clock tolerance in milliseconds, if the time on your server is running behind.
        clockTolerance: 10000,
      },
    });
  }

  return auth0;
};

export default proc;
next.config.js
const nextConfig = {
  serverRuntimeConfig: {
    appSecret: process.env.APP_SECRET,
    auth0: {
      secret: process.env.AUTH0_CLIENT_SECRET,
    },
  },
  publicRuntimeConfig: {
    baseUrl: process.env.NEXT_PUBLIC_BASE_URL,
    auth0: {
      clientId: process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID,
      domain: process.env.NEXT_PUBLIC_AUTH0_DOMAIN,
    },
  },
};

module.exports = nextConfig;
page/auth/api/sign-in.js
diff --git a/pages/api/auth/sign-in.js b/pages/api/auth/sign-in.js
index 7b109a7..d271932 100644
--- a/pages/api/auth/sign-in.js
+++ b/pages/api/auth/sign-in.js
@@ -1,6 +1,8 @@
-import auth0 from '../../../src/lib/auth0';
+import Auth0 from '../../../src/lib/auth0';
 import { checkHeaders } from '../../../src/lib/middleware/auth';

+const auth0 = Auth0();
+
 const login = checkHeaders(async (req, res) => {
   try {
     await auth0.handleLogin(req, res);

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows開発環境2020 - VScode/Docker/WSL2

WSL2

要件はバージョン 1903 以降、 ビルド 18362 以上。

手順 1 - Linux 用 Windows サブシステムを有効にする

管理者として PowerShell を開き、以下を実行します。

dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart

手順 2: 仮想マシンの機能を有効にする

管理者として PowerShell を開き、以下を実行します。

dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

終わったらPC再起動

手順 3 - Linux カーネル更新プログラム パッケージをダウンロードする

以下をインストール
- x64 マシン用 WSL2 Linux カーネル更新プログラム パッケージ

手順 4 - WSL 2 を既定のバージョンとして設定する

管理者として PowerShell を開き、以下を実行します。

wsl --set-default-version 2

手順 5 - 選択した Linux ディストリビューションをインストールする

Microsoft Store を開き、希望する Linux ディストリビューションを選択します。基本Ubuntuで良いかと。

手順 6 - 新しいディストリビューションを設定する

新しくインストールした Linux ディストリビューションを初めて起動すると、コンソール ウィンドウが開き、ファイルが圧縮解除されて初期設定が走ります。途中でユーザー名とパスワードを決めてくれと言われますので入力。

Docker Desktop

前提としてWindows 10 ProであればHyper-Vの有効化が必須。
HomeであればUEFIから設定かも。詳しくは管理者に聞いてください。

インストール

下記からDocker Desktop Windows版 をダウンロード

ダウンロードが出来たらインストーラーを実行します。
必ず「Enable WSL 2 Windows Features」にチェックが入っている事を確認してインストールしてください。

開発環境導入

ここからWSL2上で開発環境を構築していきます。
PowerShellで wsl コマンドを打つ事でWSL2に切り替えできます。

最新の状態に更新

まずは最新にしておきましょう

sudo apt update && sudo apt upgrade -y

nvmをインストール

インストール

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.0/install.sh | bash

インストール出来たか確認するためターミナルを閉じて再度WSLに入ってから下記を叩く

command -v nvm

nvmと返ってきたらインストール完了

Node.jsをインストール

安定版(LTS)をインストール

nvm install node --lts

確認します

$node -v
v14.15.1

yarnをインストール

公式ドキュメント通りにインストールします

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update && sudo apt install -y yarn

確認します

$ yarn -v 
1.22.5

同時に入る古いNodejsを削除しておく

sudo apt purge -f libuv1 nodejs
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LinuxにDockerをInstallするAnsible-Galaxy

LinuxDockerをインストールするたびに、

「ubuntu docker install」
「centos docker install」

などをググって、Docker公式サイトより手動でインストールしていました。

毎回インストールするのは手間なので、自動化したいと思っていた矢先、
OSSで素晴らしいansible-galaxyを見つけましたので、紹介したいと思います。

使用するansible-galaxy

Ansible Galaxy: https://galaxy.ansible.com/geerlingguy/docker
GitHub: https://github.com/geerlingguy/ansible-role-docker

Ansible Playbookを書いてみる

VirtualBox上にUbuntu20.04のVMを作成します。
Vagrantを用いて、Provisioning ToolとしてAnsibleを使用し、そのPlaybookでDockerをインストールします。

Vagrantfileを作成する

以下のVagrantfileを作成します。

IPアドレスは192.168.0.3/24としてあります。

# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  config.vm.define "ansible-docker" do |node|
    node.vm.box = "bento/ubuntu-20.04"
    node.vm.hostname = "ansible-docker"
    node.vm.network "private", ip: "192.168.0.3", netmask: "255.255.255.0"
    node.vm.provider "virtualbox" do |vb|
      vb.memory = "1024"
    end
  end

  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "./install_docker.yml"
    ansible.inventory_path = "./inventory.ini"
    ansible.limit = 'all'
  end
end

roleをインストールする

rolesディレクトリを作成して、その中に今回使用するroleをインストールします。

mkdir roles
ansible-galaxy install geerlingguy.docker -p roles/

InventoryとPlaybookを作る

inventory.ini

[ansible-docker]
192.168.0.3

install_docker.yml

---
hosts: all
  roles:
    role: geerlingguy.docker
    become: yes

Playbookを実行する

ディレクトリ構造は以下のようになっています。

.
├── Vagrantfile
├── install_docker.yml
├── inventory.ini
└── roles
     └── geerlingguy.docker
         ├── LICENSE
         ├── README.md
         ├── defaults
         │   └── main.yml
         ├── handlers
         │   └── main.yml
         ├── meta
         │   └── main.yml
         ├── molecule
         │   └── default
         │       ├── converge.yml
         │       └── molecule.yml
         └── tasks
             ├── docker-compose.yml
             ├── docker-users.yml
             ├── main.yml
             ├── setup-Debian.yml
             └── setup-RedHat.yml

以下のコマンドでVM作成します。(ホストOSでAnsible実行環境が整備されている必要があります。)

vagrant up

docker,docker-composeがインストールされていることを確認できます。

vagrant@ansible-docker:~$ sudo docker -v
Docker version 19.03.12, build 48a66213fe
vagrant@ansible-docker:~$ sudo docker-compose -v
docker-compose version 1.26.0, build d4451659
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dockerのcontainerが起動後すぐに停止してしまう問題を見つける方法

$ docker-compose logs

このコマンドで、docker起動時のログを見る事ができます。
僕の場合は、vueがすぐに停止してしまい、こんなエラーが出てきました。

front_1  | npm ERR! errno ENOENT
front_1  | npm ERR! app@0.1.0 serve: `vue-cli-service serve`
front_1  | npm ERR! spawn ENOENT
front_1  | npm ERR! 
front_1  | npm ERR! Failed at the app@0.1.0 serve script.
front_1  | npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
front_1  | npm WARN Local package.json exists, but node_modules missing, did you mean to install?
front_1  | 
front_1  | npm ERR! A complete log of this run can be found in:
front_1  | npm ERR!     /root/.npm/_logs/2020-11-26T00_35_26_684Z-debug.log
front_1  | 
front_1  | > app@0.1.0 serve /app
front_1  | > vue-cli-service serve
front_1  | 
front_1  | sh: vue-cli-service: not found
front_1  | npm ERR! code ELIFECYCLE
front_1  | npm ERR! syscall spawn
front_1  | npm ERR! file sh
front_1  | npm ERR! errno ENOENT
front_1  | npm ERR! app@0.1.0 serve: `vue-cli-service serve`
front_1  | npm ERR! spawn ENOENT
front_1  | npm ERR! 
front_1  | npm ERR! Failed at the app@0.1.0 serve script.
front_1  | npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
front_1  | npm WARN Local package.json exists, but node_modules missing, did you mean to install?
front_1  | 
front_1  | npm ERR! A complete log of this run can be found in:
front_1  | npm ERR!     /root/.npm/_logs/2020-11-26T00_36_01_318Z-debug.log
front_1  | mba:project hoge$ 

原因がわかったので、これをググります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む