11 Release Handling
Erlang リファレンスマニュアル で説明されている通り、実行時にモジュールコードを変更したり、コードを置き換えられるというのが、Erlangというプログラミング言語の重要な機能です。
この機能に基づいて、OTPアプリケーションSASL(System Application Support Libraries)は、実行時に、異なったバージョン間でアップグレードしたり、ダウングレードしたりできるフレームワークを提供しています。これが リリース・ハンドリング です。
このフレームワークは、スクリプトの生成やリリースパッケージといったオフラインのサポート(systools)と、リリースパッケージの展開とインストールといったオンラインのサポート(release_handler)の2つの部分から構成されています。
リリース・ハンドリングを有効にするためのErlang/OTPの最小システム構成は、Kernel、STDLIB、SASLの3つになります。
新しいバージョンのリリースのインストールにも、リリース・ハンドラが利用されます。これは、 relup 内の命令を評価するによって行われます。モジュールが追加されたり、削除されたり、リロードされたり、アプリケーションが起動されたり、停止されたり、再起動されたりします。場合によっては、エミュレータ全体の再起動が必要となります。
もしインストールが失敗すると、システムが再起動し、古いリリースバージョンが自動的に使用されます。
次の appupクックブック の章では、実行時に簡単にアップグレード、ダウングレードを行うための、典型的な .appup ファイルのサンプルが書かれています。しかし、リリース・ハンドリングは複雑で、さまざまな側面があります。
そのため、なるべく小さなステップで、後方互換性を維持しながらコードを変更していくことが推奨されます。
リリース・ハンドリングをうまく動かすためには、現在はどのリリースを実行しているかを、ランタイムシステムが把握している必要があります。これは、失敗時などのシステムの再起動時に、どのブートスクリプトや、システム設定ファイルを読み込むべきか、などです。これにより、実行時も含めて変更が行えるようになります。ErlangがErlangを組み込みのシステムとして起動しなければならない場合は、これをどのように行えばよいのかは、 embedded_system を参照してください。
適切に動作させるためにシステムの再起動をする場合、ハートビートモニター付きでシステムを起動する必要があることもあります。これについては、 erl(1) と heart(3) を参照してください。
他の要件には次のようなものがあります。
リリースパッケージ内に含めるブートスクリプトは、リリースパッケージを作成したのと同じ .rel ファイルから作成されなければなりません。
アップグレードやダウングレードが行われる時に、このスクリプトからアプリケーションに関する情報が収集されます。
システムは、 sys.config と呼ばれるただ一つのシステム設定ファイルを使って行われる必要があります。
リリースパッケージの作成時にこのファイルが見つかると、自動的に取り込まれます。
システムがいくつかのErlangノードから構成されている場合、それぞれのノードは、それぞれごとに違うバージョンのリリースを使用しているかもしれません。リリース・ハンドラはローカルに登録されたプロセスで、アップグレードやダウングレードが必要なそれぞれのノード上で呼び出されなければなりません。複数のノードのリリースハンドラプロセスを同期させることが可能な命令があります。それが sync_nodes です。詳しくは appup(4) を参照してください。
OTPは .appup ファイルを作成する時に使用できる、 リリース・ハンドリング命令 を提供しています。リリースハンドラは、それらの命令のサブセットの、 低レベルな命令 を理解することができます。ユーザから簡単に使用できる方法としては、 高レベルな命令 もあります。これらの命令は、 systools:make_relup によって低レベルな命令に変換されます。
ここでは、もっとも良く利用される命令について説明します。利用可能なすべての命令については、 appup(4) を参照してください。
最初にいくつかの定義を示します。
レジデンスモジュール
このモジュールは、プロセスが末尾再帰ループをする関数を持ちます。もし、末尾再帰ループ関数が複数のモジュールで実装されているのであれば、プロセスから見ると、これらのモジュールはすべてレジデンスモジュールです。
機能性モジュール
レジデンスモジュールではない、モジュールです。
OTPビヘイビアを利用してプロセスを実装すると、ビヘイビアモジュールがレジデンスモジュールとなります。コールバックモジュールは機能性モジュールです。
もし、機能性モジュールに関する単純な拡張を行ったのであれば、システムに新しいバージョンのモジュールをロードして、古いバージョンを削除するだけで十分です。これは シンプルコード交換 と呼ばれていて、次の命令を使用して行います。
{load_module, Module}
gen_serverの内部ステートの形式が変更されたなど、より複雑な変更を加えた場合には、単純なコード交換では不十分です。代わりに、モジュールを使用しているプロセスを中断させ(コード交換が完了する前にリクエストを受けるのを避けるため)、内部ステートの形式を変換して、新しいバージョンのモジュールに切り替えて、最後に古いバージョンを削除し、プロセスを再開する必要があります。これは、 同期コード交換 と呼ばれ、次の命令によって行います。
{update, Module, {advanced, Extra}}
{update, Module, supervisor}
{advanced,Extra} という引数を渡して update というのは、上の説明でしたように、内部ステートを変更するときに使用します。これは、ビヘイビアプロセスに対して、 Extra と、その他のいくつかの情報を引数の渡して、コールバック関数の code_change を呼び出します。詳細については、それぞれのビヘイビアのmanページと、 appupクックブック を参照してください。
スーパバイザの起動の仕様が変わる場合には、引数付きで、スーパバイザに対して update が使用されます。 appupクックブック を参照してください。
リリースハンドラは、現在実行中のアプリケーションの監視ツリーを探索し、次に挙げる形式の、すべての子アプリケーションの仕様を調べて、アップデートするモジュールを使用しているプロセスを探します。
{Id, StartFunc, Restart, Shutdown, Type, Modules}
もし、子プロセスの仕様の中の Modules に、リストアップされている中にモジュールの名前があれば、そのプロセスは更新予定のモジュールを仕様しているということになります。
イベントマネージャの場合には、 Modules=dynamic となります。この場合は、イベントマネージャプロセスは、現在インストールされているイベントハンドラ(gen_fsm)のリストをリリースハンドラに知らせ、更新予定のモジュール名がそのリストに含まれているかどうかチェックします。
リリースハンドラは sys:suspend/1,2 、 sys:change_code/4,5 、 sys:resume/1,2 を呼び出すことで、プロセスを停止させ、コードの変更の問い合わせを行い、レジュームを行います。
新しいモジュールが導入される場合には、次の命令が使用されます。
{add_module, Module}
この命令はモジュールをロードします。この命令は、Erlangが組み込みモードでで実行しているときは絶対に必要となります。デフォルトの対話モードでErlangを実行しているときは、コードサーバが自動的にロードされていないモジュールをロードしにいくため、厳密には不要です。
add_module の反対が delete_module です。これはモジュールをアンロードします。
{delete_module, Module}
この命令が実行される時は、モジュールがレジデンス・モードの場合、どんなアプリケーション内であっても、プロセスがキルされることに注意してください。ユーザは、モジュールの削除前にそのようなプロセスをすべて終了させて、スーパバイザの再起動時に失敗するという状況をなるべく避けるようにすべきです。
アプリケーションを追加する際に使用する命令は次の通りです。
{add_application, Application}
アプリケーションの追加が行われると、 .app ファイルの modules キーで定義されたモジュールに対して、 add_module 命令を使ってロードし、その後、アプリケーションが起動します。
アプリケーションの削除には次の命令を使います。
{remove_application, Application}
アプリケーションの削除が行われると、アプリケーションが停止され、 delete_module 命令を使用してモジュールをアンロードし、最後にアプリケーション仕様をアプリケーションコントローラから削除します。
アプリケーションの再起動には次の命令を使います。
{restart_application, Application}
アプリケーションの再起動を行うと、ちょうど remove_application と add_application を連続して使用したように、アプリケーションが停止され、その後再起動します。
リリースハンドラから何らかの関数を呼ぶには、次の命令が使用されます。
{apply, {M, F, A}}
リリースハンドラは apply(M, F, A) という式を評価します。
この命令はエミュレータのバージョンを更新したり、なんらかの理由でシステムの再起動が必要になった時に使用されます。このためには、ハートビートモニターを使って再起動を行う必要があります。詳しくは erl(1) と heart(3) を参照してください。
リリースハンドラがこの命令に遭遇すると、 init:reboot() を呼び出して、現在のエミュレータを停止します。詳しくは init(3) を参照してください。すべてのプロセスが正常通り終了したら、 heart プログラムによってシステムが再起動され、新しいリリースバージョンが使用されます。新しいエミュレータが起動して実行されている場合には、新バージョンを永続化しなければなりません。そうでないと、新システムのリブートに古いバージョンが使用されてしまいます。
UNIX上では、リリースハンドラは、 heart プログラムに対して、システムのリブートに使うコマンドを伝えます。通常は HEART_COMMAND 環境変数が使用されますが、この場合は無視されます。このコマンドのデフォルトは $ROOT/bin/start です。SASLの設定パラメータの start_prg を使用すると、他のコマンドを使用することができます。詳しくは、 sasl(6) を参照してください。
現在のバージョンと、以前のバージョン間でアップグレード、ダウングレードを行う場合には、アプリケーション・アップグレードファイルを作成する必要があります。これは短く、 .appup ファイルとも呼ばれます。ファイル名は Application.appup という名前になります。この Application の部分はアプリケーション名になります。
{Vsn,
[{UpFromVsn1, InstructionsU1},
...,
{UpFromVsnK, InstructionsUK}],
[{DownToVsn1, InstructionsD1},
...,
{DownToVsnK, InstructionsDK}]}.
Vsn は文字列型で、 .app ファイルに記述された、現在のバージョンです。それぞれの UpFromVsn はアップグレード元のアプリケーションのバージョンです。また、 DownToVsn は、ダウングレード先のバージョンです。それぞれの命令は、リリースハンドリングの命令のリストになります。
appup ファイルの文法や内容の詳細は appup(4) で説明されている。
appupクックブック の章では、様々なアップグレード/ダウングレードの状況別の、 .appup ファイルのサンプルが紹介されています。
サンプル: release の章で紹介した ch_rel-1 を使って紹介します。
Note
このサンプルを実行する場合には、オリジナルのディレクトリのコピーを行って、その中で変更を加えるようにしてください。そうすれば、元のバージョンを残すことができます。
-module(ch3).
-behaviour(gen_server).
-export([start_link/0]).
-export([alloc/0, free/1]).
-export([available/0]).
-export([init/1, handle_call/3, handle_cast/2]).
start_link() ->
gen_server:start_link({local, ch3}, ch3, [], []).
alloc() ->
gen_server:call(ch3, alloc).
free(Ch) ->
gen_server:cast(ch3, {free, Ch}).
available() ->
gen_server:call(ch3, available).
init(_Args) ->
{ok, channels()}.
handle_call(alloc, _From, Chs) ->
{Ch, Chs2} = alloc(Chs),
{reply, Ch, Chs2};
handle_call(available, _From, Chs) ->
N = available(Chs),
{reply, N, Chs}.
handle_cast({free, Ch}, Chs) ->
Chs2 = free(Ch, Chs),
{noreply, Chs2}.
次に新しいバージョンの ch_app.app ファイルを作る必要があります。バージョン番号が更新されています。
{application, ch_app,
[{description, "Channel allocator"},
{vsn, "2"},
{modules, [ch_app, ch_sup, ch3]},
{registered, [ch3]},
{applications, [kernel, stdlib, sasl]},
{mod, {ch_app,[]}}
]}.
ch_app を1から2にアップグレード(もしくは2から1にダウングレード)する場合は、単に新(もしくは旧)の ch3 コールバックモジュールをロードするだけです。 ebin ディレクトリの中に、 ch_app.appup アップグレードファイルを作ります。
{"2",
[{"1", [{load_module, ch3}]}],
[{"1", [{load_module, ch3}]}]
}.
リリースの現在のバージョンと前のバージョン間で、アップグレード/ダウングレードする方法を定義するには、リリース・アップグレードファイルを作成します。これは短縮して、 relup ファイルと呼ばれます。
このファイルは、 systools:make_relup/3,4 が作成するため、手動で作る必要はありません。それぞれのバージョンの .rel ファイル、 .app ファイル、 .appup ファイルが入力として使用されます。どのアプリケーションを追加/削除すべきか、どのアプリケーションのアップグレード/ダウングレードが必要なのか、という情報を抽出する必要があります。これに対する命令が .appup から取得され、正しく並んでいる単一の低レベルの命令のリストに変換されます。
もし relup ファイルが比較的シンプルであれば、手動で作ることもできます。ただし、それには低レベルの命令だけしか含められないことに注意してください。
リリースアップグレードファイルの構文と内容については、 relup(4) で詳細に説明しています。
例えば、前のセクションのサンプルについて、 ch_app の新バージョンの2と、 .appup ファイルを持っていたとします。新しいバージョンの .rel ファイルが必要になります。このファイルを ch_rel-2.rel という名前にしたとして、リリースバージョン文字列を A から B に変えます。
{release,
{"ch_rel", "B"},
{erts, "5.3"},
[{kernel, "2.9"},
{stdlib, "1.12"},
{sasl, "1.10"},
{ch_app, "2"}]
}.
relup ファイルが生成できるようになりました。
1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"]).
ok
この関数を起動することで、 バージョンA(ch_rel-1)からバージョンB(ch_rel-2)にアップグレードする命令と、バージョンBからバージョンAにダウングレードする命令を含む relup ファイルが生成されます。
.app と .rel の新旧の両バージョンとも、 .appup や新しい .beam ファイルと一緒に、コードのパスのなかに置かなければなりません。オプションのパスを設定することで、コードのパスを増やすことができます。
1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"],
[{path,["../ch_rel-1",
"../ch_rel-1/lib/ch_app-1/ebin"]}]).
ok
新しいバージョンのリリースを作ると、リリースパッケージを作って、ターゲット環境に送信することができます。
新しいバージョンのリリースを実行時にインストールするには、リリースハンドラを仕様します。これは、SASLアプリケーションに属したプロセスで、パッケージの展開、インストール、リリースパッケージの削除などを行います。 release_handler モジュールがこの機能のインタフェースになっています。これについては、 release_handler(3) を参照してください。
ターゲットシステムのインストールのルートディレクトリは $ROOT であると想定して処理が行われます。新しいバージョンのリリースを含めたリリースパッケージは $ROOT のコピーされなければなりません。
最初のアクションは、リリースパッケージの展開です。まずはパッケージからファイルを抽出します。
release_handler:unpack_release(ReleaseName) => {ok, Vsn}
ReleaseName は、リリースパッケージの名前から .tar.gz という拡張子外した名前です。 Vsn は、展開したリリースの .rel ファイルのバージョンです。
$ROOT/lib/releases/Vsn というディレクトリが作られ、 .rel ファイルやブートスクリプトの start.boot 、システム構成ファイルの sys.config 、`file:relup` が置かれます。バージョン番号ごとのアプリケーションのために、 $ROOT/lib 以下にアプリケーションディレクトリが置かれます。変更されていないアプリケーションには適用されません。
展開されたリリースはインストールすることができます。リリースハンドラは、 relup 中に書かれた命令を評価して行きます。
release_handler:install_release(Vsn) => {ok, FromVsn, []}
もしインストール中にエラーが発生すると、古いバージョンのリリースを使って再起動を行います。もしインストールが成功すると、新しいバージョンのリリースを使うようになりますが、何かが発生してシステムを再起動すると、以前のバージョンを使って起動してしまいます。この新しいバージョンをデフォルトのバージョンに設定するために、新しいバージョンを永続化しなければなりません。こうすると、以前のバージョンは古いバージョンとなります。
release_handler:make_permanent(Vsn) => ok
システムは、 $ROOT/releases/RELEASES と $ROOT/releases/start_erl.data の中に、どのバージョンが古く、どのバージョンが永続化されているのか、という情報を保持しています。
Vsn から FromVsn にダウングレードする場合は、 install_release を再び呼び出します。
release_handler:install_release(FromVsn) => {ok, Vsn, []}
インストールされているが、永続化されていないリリースは削除することができます。リリースに対する情報は ROOT/releases/RELEASES から削除され、新しいアプリケーションのディレクトリと、 $ROOT/releases/Vsn に格納されている、リリースに関連するコードも削除されます。
release_handler:remove_release(Vsn) => ok
サンプル: 前のセクションからの続きのサンプルで説明をします。
システム原則 で説明した通りに、:ref:release の章で説明した ch_rel の最初のバージョン”A”を作成します。今回は、 sys.config がリリースパッケージに含まれていなければなりません。もし設定するものがなければ、ファイルには空のリストを書きます。
[].
シンプルなターゲットのシステムから、システムをスタートさせます。実際には、組み込みのシステムから開始します。ですが、説明するという目的でいえば、適切なブートスクリプトと、 .config ファイルがあれば十分です。
% cd $ROOT
% bin/erl -boot $ROOT/releases/A/start -config $ROOT/releases/A/sys
...
$ROOT はターゲットシステムのインストールディレクトリです。
他のErlangシェルを起動して、スタートスクリプトを作成し、新しいバージョン”B”のリリースパッケージを作成します。これには、 sys.config と、 relup ファイル(できれば更新して)を含めるようにしてください。詳しくは リリース・アップグレードファイル を参照してください。
1> systools:make_script("ch_rel-2").
ok
2> systools:make_tar("ch_rel-2").
ok
新しいリリースパッケージに、 ch_app のバージョン”2”と、 relup ファイルが含まれました。
% tar tf ch_rel-2.tar
lib/kernel-2.9/ebin/kernel.app
lib/kernel-2.9/ebin/application.beam
...
lib/stdlib-1.12/ebin/stdlib.app
lib/stdlib-1.12/ebin/beam_lib.beam
...
lib/sasl-1.10/ebin/sasl.app
lib/sasl-1.10/ebin/sasl.beam
...
lib/ch_app-2/ebin/ch_app.app
lib/ch_app-2/ebin/ch_app.beam
lib/ch_app-2/ebin/ch_sup.beam
lib/ch_app-2/ebin/ch3.beam
releases/B/start.boot
releases/B/relup
releases/B/sys.config
releases/ch_rel-2.rel
起動中のターゲットシステムの中で、リリースパッケージを展開します。
1> release_handler:unpack_release("ch_rel-2").
{ok,"B"}
新しいアプリケーションバージョン ch_app-2 が $ROOT/lib の ch_app-1 の隣にインストールされます。 kernel 、 stdlib 、 sasl などは変更されていないため、このディレクトリは影響を受けません。
$ROOT/releases 以下には、新しい B というディレクトリが作られ、その中に ch_rel-2.rel 、 start.boot 、 sys.config 、 relup が格納されます。
ch3:available/0 関数が呼び出せるか確認をします。
2> ch3:available().
** exception error: undefined function ch3:available/0
新しいリリースをインストールします。 $ROOT/releases/B/relup に含まれている命令が1つずつ実行され、 ch3 の新バージョンがロードされて、 ch3:available/0 関数が利用可能になります。
3> release_handler:install_release("B").
{ok,"A",[]}
4> ch3:available().
3
5> code:which(ch3).
".../lib/ch_app-2/ebin/ch3.beam"
6> code:which(ch_sup).
".../lib/ch_app-1/ebin/ch_sup.beam"
この時点では、 ch_app 内のプロセスはまだアップデートされません。スーパバイザのサンプルと同じく、この時点では、まだ ch_app-1 のコードを評価しています。
このときにターゲットのシステムがリブートされても、まだバージョンAが使用されます。システムの再起動時にバージョンBが使われるようにするには、永続化しなければなりません。
7> release_handler:make_permanent("B").
ok
新しいバージョンのリリースがインストールされる時は、ロードされているすべてのアプリケーションのアプリケーション仕様が自動的に更新されます。
Note
新しいアプリケーション仕様の情報はリリースパッケージに含まれる、ブートスクリプトが読み込みに行きます。そのため、ブートスクリプトはリリースパッケージを作ったときに使用されたのと同じ .rel ファイルが使用されます。
アプリケーション設定パラメーターは、次のような優先度で自動的に更新されます。
パラメーター値は、他のシステム設定ファイルや、 application:set_env/3 を使用して設定された値は無視されます。
インストールされたリリースが永続化される時に、システムプロセスの init は、新しい sys.config を見に行くように設定されます。
インストールされると、アプリケーションコントローラーは、実行中のすべてのアプリケーションの新旧の設定パラメーターを比較して、コールバック関数を呼び出します。
Module:config_change(Changed, New, Removed)
Module は、 .app ファイルの mod キーで定義された、アプリケーションのコールバックモジュールです。 Changed と New には、 {Par, Val} という形式のリストで、すべての設定値の変更や追加を個別に書かれます。 Removed には、すべての削除される Par のリストです。
この関数はオプションなので、アプリケーションのコールバックモジュールに実装するかどうかは任意で、省略することも可能です。
Copyright (c) 1991-2009 Ericsson AB