3.创建和升级目标系统 | 3. Creating and Upgrading a Target System

3建立和升级目标系统

在使用Erlang/OTP创建系统时,最简单的方法是在某处安装Erlang/OTP,在其他位置安装特定于应用程序的代码,然后启动Erlang运行时系统,确保代码路径包含特定于应用程序的代码。

按原样使用Erlang / OTP系统通常是不理想的。开发人员可以为特定目的创建新的符合Erlang / OTP的应用程序,并且几个原始的Erlang / OTP应用程序可能与所讨论的目的无关。因此,需要能够基于给定的Erlang / OTP系统创建新的系统,其中可移除的应用程序被移除并且包括新的应用程序。文档和源代码是不相关的,因此不包含在新系统中。

本章是关于创建这样一个称为目标系统的系统

以下部分讨论具有不同功能要求的目标系统:

  • 一个基本的目标系统,可以通过调用普通的启动erl脚本。

  • 一个简单的目标系统,可以在运行时执行代码替换。

  • 一个嵌入式目标系统,其中也有从系统日志输出到文件供以后检查的支持,并在该系统可在启动时自动启动。

Erlang/OTP在UNIX系统上运行时,只考虑这种情况。

sasl应用程序包含示例Erlang模块target_system.erl,其中包含用于创建和安装目标系统的功能。以下示例中使用了该模块。该模块的源代码列在Listing of target_system.erl

3.1创建目标系统

假设您有一个工作的Erlang/OTP系统,该系统按照OTP设计原则进行结构。

步骤1.创建一个.rel文件(请参阅rel(4)SASL中的手册页),该文件指定了ERTS版本并列出了要包含在新基本目标系统中的所有应用程序。一个例子是以下mysystem.rel文件:

%% mysystem.rel {release, {"MYSYSTEM", "FIRST"}, {erts, "5.10.4"}, [{kernel, "2.16.4"}, {stdlib, "1.19.4"}, {sasl, "2.3.4"}, {pea, "1.0"}]}.

列出的应用程序不仅是原始的Erlang/OTP应用程序,而且可能还包括您编写的新应用程序(这里以应用程序Pea(pea)为例)。

第二步。mysystem.rel文件驻留:

os> erl -pa /home/user/target_system/myapps/pea-1.0/ebin

这里也pea-1.0提供了ebin目录的路径。

第三步。创建目标系统:

1> target_system:create("mysystem").

target_system:create/1功能执行以下操作:

  • 读取文件mysystem.rel并创建一个plain.rel与前者相同的新文件,只是它仅列出内核和STDLIB应用程序。

  • 从文件mysystem.relplain.rel创建的文件mysystem.scriptmysystem.bootplain.script,并plain.boot通过一个呼叫systools:make_script/2

  • mysystem.tar.gz通过呼叫创建文件systools:make_tar/2。该文件具有以下内容:

erts-5.10.4/bin/ releases/FIRST/start.boot releases/FIRST/mysystem.rel releases/mysystem.rel lib/kernel-2.16.4/ lib/stdlib-1.19.4/ lib/sasl-2.3.4/ lib/pea-1.0/

文件releases/FIRST/start.boot是我们的副本mysystem.boot

释放资源文件mysystem.rel在tar文件中被复制。最初,该文件只存储在releases目录中,以便可以release_handler单独提取此文件。解压tar文件后,release_handler会自动将文件复制到releases/FIRST。但是,有时候tar文件会被解压缩而不涉及release_handler(例如,在解包第一个目标系统时)。因此该文件现在改为在tar文件中复制,因此不需要手动复制。

  • 创建临时目录tmp并将tar文件解压缩mysystem.tar.gz到该目录中。

  • 删除文件erlstarttmp/erts-5.10.4/bin。安装发行版时,这些文件是从源再次创建的。

  • 创建目录tmp/bin

  • 将先前创建的文件复制plain.boottmp/bin/start.boot

  • 将文件epmdrun_erlto_erl目录复制tmp/erts-5.10.4/bin到目录tmp/bin

  • 创建目录tmp/log,如果系统以嵌入bin/start脚本的方式启动,则使用该目录。

  • 创建tmp/releases/start_erl.data内容为“5.10.4 FIRST” 的文件。该文件将作为数据文件传递给start_erl脚本。

  • mysystem.tar.gz从目录中的目录重新创建文件tmp并删除tmp

3.2安装目标系统

第4步。将创建的目标系统安装在合适的目录中。

2> target_system:install("mysystem", "/usr/local/erl-target").

该功能target_system:install/2执行以下操作:

  • 将tar文件解压缩mysystem.tar.gz到目标目录中/usr/local/erl-target

  • 在目标目录中读取文件releases/start_erl.data以查找Erlang运行时系统版本(“5.10.4”)。

  • 替代品%FINAL_ROOTDIR%%EMU%用于/usr/local/erl-targetbeam分别,在文件中erl.srcstart.src以及start_erl.src目标erts-5.10.4/bin,目录,并把生成的文件erlstart以及run_erl在目标bin目录中。

  • 最后,根据releases/RELEASES文件中的数据创建目标文件releases/mysystem.rel

3.3启动目标系统

现在我们有一个可以以各种方式启动的目标系统。我们通过调用以下作为基本目标系统来启动它:

os> /usr/local/erl-target/bin/erl

这里只启动内核和STDLIB应用程序,即系统以普通开发系统启动。所有这些工作只需要两个文件:

  • bin/erl(从中获得erts-5.10.4/bin/erl.src

  • bin/start.boot(副本plain.boot

我们也可以启动分布式系统(需要bin/epmd)。

要启动在原始mysystem.rel文件中指定的所有应用程序,请-boot按如下所示使用标志:

os> /usr/local/erl-target/bin/erl -boot /usr/local/erl-target/releases/FIRST/start

我们如上所述开始一个简单的目标系统。唯一的区别是该文件releases/RELEASES也存在于运行时的代码替换工作。

要启动嵌入式目标系统,请使用shell脚本bin/start。脚本调用bin/run_erl,然后调用bin/start_erl(粗略地说,start_erl是嵌入式变体erl)。

start在安装过程中从erts-5.10.4/bin/start.src生成的shell脚本只是一个示例。编辑它以满足您的需求。通常在UNIX系统启动时执行。

run_erl是一个包装器,它提供了将运行时系统的输出记录到文件中。它还提供了一个附加到Erlang shell(to_erl)的简单机制。

start_erl要求:

  • 根目录("/usr/local/erl-target"

  • 发行版目录("/usr/local/erl-target/releases"

  • 文件的位置 start_erl.data

它执行下列工作:

  • 读取运行时系统版本("5.10.4")并从start_erl.data文件中释放版本("FIRST")。

  • 启动找到的版本的运行时系统。

  • 提供-boot指定发现版本found("releases/FIRST/start.boot")的引导文件的标志。

start_erl还假定sys.config在发行版本目录("releases/FIRST/sys.config")中有。这是下一节的主题。

start_erlshell脚本通常不是由用户来改变。

3.4系统配置参数

如前一节所述,start_erl需要sys.config在发行版本目录中("releases/FIRST/sys.config")。如果没有这样的文件,则系统启动失败。因此也必须添加这样的文件。

如果系统配置数据既不依赖于文件位置也不依赖于站点,因此可以方便地sys.config提前创建,因此它成为由目标系统创建的tar文件的一部分target_system:create/1。实际上,如果你在当前目录下创建的不仅仅是文件mysystem.rel,还有文件sys.config,后一个文件默认放在相应的目录中。

3.5与安装脚本的差异

前面的install/2过程与普通的Installshell脚本有所不同。事实上,create/1尽可能使发布包完整,并且install/2仅通过考虑与位置有关的文件来完成该过程。

3.6创建下一个版本

在这个例子中,Pea应用程序已经被改变,应用程序ERTS,Kernel,STDLIB和SASL也被更改了。

第1步。创建文件.rel

%% mysystem2.rel {release, {"MYSYSTEM", "SECOND"}, {erts, "6.0"}, [{kernel, "3.0"}, {stdlib, "2.0"}, {sasl, "2.4"}, {pea, "2.0"}]}.

第2步。appup(4)为Pea 创建应用程序升级文件(请参阅SASL 的手册页),例如:

%% pea.appup {"2.0", [{"1.0",[{load_module,pea_lib}]}], [{"1.0",[{load_module,pea_lib}]}]}.

第3步。从文件mysystem2.rel所在的目录中,启动Erlang/OTP系统,为新版本的Pea提供路径:

os> erl -pa /home/user/target_system/myapps/pea-2.0/ebin

第4步。创建发行版升级文件(请参阅relup(4)SASL中的手册页):

1> systools:make_relup("mysystem2",["mysystem"],["mysystem"], [{path,["/home/user/target_system/myapps/pea-1.0/ebin", "/my/old/erlang/lib/*/ebin"]}]).

这儿,"mysystem"是基础版本,"mysystem2"是要升级到的版本。

path选项用于指出所有应用程序的旧版本。(新版本已经在代码路径中了 - 当然假设执行此操作的Erlang节点正在运行正确版本的Erlang/OTP。)

第五步。创建新版本:

2> target_system:create("mysystem2").

鉴于relup步骤4中生成的文件现在位于当前目录中,它将自动包含在发行包中。

3.7提升目标系统

这部分是在目标节点上完成的,在这个例子中,我们希望节点作为一个带有-heart选项的嵌入式系统运行,从而允许节点自动重新启动。有关更多信息,请参阅Starting a Target System

我们添加-heartbin/start

#!/bin/sh ROOTDIR=/usr/local/erl-target/ if [ -z "$RELDIR" ] then RELDIR=$ROOTDIR/releases fi START_ERL_DATA=${1:-$RELDIR/start_erl.data} $ROOTDIR/bin/run_erl -daemon /tmp/ $ROOTDIR/log "exec $ROOTDIR/bin/start_erl $ROOTDIR\ $RELDIR $START_ERL_DATA -heart

我们使用最简单的sys.config,我们存储在releases/FIRST

%% sys.config [].

最后,为了准备升级,我们必须将新版本软件包放入releases第一个目标系统的目录中:

os> cp mysystem2.tar.gz /usr/local/erl-target/releases

假设节点已经启动如下:

os> /usr/local/erl-target/bin/start

它可以被访问如下:

os> /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.1

日志可以在中找到/usr/local/erl-target/log。该目录被指定为run_erl上面列出的启动脚本中的参数。

第1步。解压缩发行版:

1> {ok,Vsn} = release_handler:unpack_release("mysystem2").

第二步。安装发行版:

2> release_handler:install_release(Vsn). {continue_after_restart,"FIRST",[]} heart: Tue Apr 1 12:15:10 2014: Erlang has closed. heart: Tue Apr 1 12:15:11 2014: Executed "/usr/local/erl-target/bin/start /usr/local/erl-target/releases/new_start_erl.data" -> 0. Terminating. [End]

上述返回值和调用后的输出release_handler:install_release/1意味着release_handler已重新启动节点heart。这通常在升级涉及应用程序ERTS,Kernel,STDLIB或SASL的更改时完成。有关更多信息,请参阅Upgrade when Erlang/OTP has Changed

该节点可通过新管道访问:

os> /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.2

检查系统中有哪些版本:

1> release_handler:which_releases(). [{"MYSYSTEM","SECOND", ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"], current}, {"MYSYSTEM","FIRST", ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"], permanent}]

我们的新版本“SECOND”现在是最新版本,但我们也可以看到我们的“FIRST”版本仍然是永久性的。这意味着如果节点现在会重新启动,它会再次运行“FIRST”版本。

第3步。使新版本永久:

2> release_handler:make_permanent("SECOND").

再次检查发布:

3> release_handler:which_releases(). [{"MYSYSTEM","SECOND", ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"], permanent}, {"MYSYSTEM","FIRST", ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"], old}]

我们看到新版本是permanent,所以重新启动节点是安全的。

3.8 target_system.erl的列表

该模块也可以在examplesSASL应用程序的目录中找到。

-module(target_system). -export([create/1, create/2, install/2]). %% Note: RelFileName below is the *stem* without trailing .rel, %% .script etc. %% %% create(RelFileName) %% create(RelFileName) -> create(RelFileName,[]). create(RelFileName,SystoolsOpts) -> RelFile = RelFileName ++ ".rel", Dir = filename:dirname(RelFileName), PlainRelFileName = filename:join(Dir,"plain"), PlainRelFile = PlainRelFileName ++ ".rel", io:fwrite("Reading file: ~tp ...~n", [RelFile]), {ok, [RelSpec]} = file:consult(RelFile), io:fwrite("Creating file: ~tp from ~tp ...~n", [PlainRelFile, RelFile]), {release, {RelName, RelVsn}, {erts, ErtsVsn}, AppVsns} = RelSpec, PlainRelSpec = {release, {RelName, RelVsn}, {erts, ErtsVsn}, lists:filter(fun{kernel, _}) -> true; {stdlib, _}) -> true; (_) -> false end, AppVsns) }, {ok, Fd} = file:open(PlainRelFile, [write]), io:fwrite(Fd, "~p.~n", [PlainRelSpec]), file:close(Fd), io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n", [PlainRelFileName,PlainRelFileName]), make_script(PlainRelFileName,SystoolsOpts), io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n", [RelFileName, RelFileName]), make_script(RelFileName,SystoolsOpts), TarFileName = RelFileName ++ ".tar.gz", io:fwrite("Creating tar file ~tp ...~n", [TarFileName]), make_tar(RelFileName,SystoolsOpts), TmpDir = filename:join(Dir,"tmp"), io:fwrite("Creating directory ~tp ...~n",[TmpDir]), file:make_dir(TmpDir), io:fwrite("Extracting ~tp into directory ~tp ...~n", [TarFileName,TmpDir]), extract_tar(TarFileName, TmpDir), TmpBinDir = filename:join([TmpDir, "bin"]), ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]), io:fwrite("Deleting \"erl\" and \"start\" in directory ~tp ...~n", [ErtsBinDir]), file:delete(filename:join([ErtsBinDir, "erl"])), file:delete(filename:join([ErtsBinDir, "start"])), io:fwrite("Creating temporary directory ~tp ...~n", [TmpBinDir]), file:make_dir(TmpBinDir), io:fwrite("Copying file \"~ts.boot\" to ~tp ...~n", [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]), copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])), io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n" "~tp to ~tp ...~n", [ErtsBinDir, TmpBinDir]), copy_file(filename:join([ErtsBinDir, "epmd"]), filename:join([TmpBinDir, "epmd"]), [preserve]), copy_file(filename:join([ErtsBinDir, "run_erl"]), filename:join([TmpBinDir, "run_erl"]), [preserve]), copy_file(filename:join([ErtsBinDir, "to_erl"]), filename:join([TmpBinDir, "to_erl"]), [preserve]), %% This is needed if 'start' script created from 'start.src' shall %% be used as it points out this directory as log dir for 'run_erl' TmpLogDir = filename:join([TmpDir, "log"]), io:fwrite("Creating temporary directory ~tp ...~n", [TmpLogDir]), ok = file:make_dir(TmpLogDir), StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]), io:fwrite("Creating ~tp ...~n", [StartErlDataFile]), StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]), write_file(StartErlDataFile, StartErlData), io:fwrite("Recreating tar file ~tp from contents in directory ~tp ...~n", [TarFileName,TmpDir]), {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]), %% {ok, Cwd} = file:get_cwd(), %% file:set_cwd("tmp"), ErtsDir = "erts-"++ErtsVsn, erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []), erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []), erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []), erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []), erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []), erl_tar:close(Tar), %% file:set_cwd(Cwd), io:fwrite("Removing directory ~tp ...~n",[TmpDir]), remove_dir_tree(TmpDir), ok. install(RelFileName, RootDir) -> TarFile = RelFileName ++ ".tar.gz", io:fwrite("Extracting ~tp ...~n", [TarFile]), extract_tar(TarFile, RootDir), StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]), {ok, StartErlData} = read_txt_file(StartErlDataFile), [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"), ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]), BinDir = filename:join([RootDir, "bin"]), io:fwrite("Substituting in erl.src, start.src and start_erl.src to " "form erl, start and start_erl ...\n"), subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir, [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}], [preserve]), %%! Workaround for pre OTP 17.0: start.src and start_erl.src did %%! not have correct permissions, so the above 'preserve' option did not help ok = file:change_mode(filename:join(BinDir,"start"),8#0755), ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755), io:fwrite("Creating the RELEASES file ...\n"), create_RELEASES(RootDir, filename:join([RootDir, "releases", filename:basename(RelFileName)])). %% LOCALS %% make_script(RelFileName,Opts) %% make_script(RelFileName,Opts) -> systools:make_script(RelFileName, [no_module_tests, {outdir,filename:dirname(RelFileName)} |Opts]). %% make_tar(RelFileName,Opts) %% make_tar(RelFileName,Opts) -> RootDir = code:root_dir(), systools:make_tar(RelFileName, [{erts, RootDir}, {outdir,filename:dirname(RelFileName)} |Opts]). %% extract_tar(TarFile, DestDir) %% extract_tar(TarFile, DestDir) -> erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]). create_RELEASES(DestDir, RelFileName) -> release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel"). subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) -> lists:foreach(fun(Script) -> subst_src_script(Script, SrcDir, DestDir, Vars, Opts) end, Scripts). subst_src_script(Script, SrcDir, DestDir, Vars, Opts) -> subst_file(filename:join([SrcDir, Script ++ ".src"]), filename:join([DestDir, Script]), Vars, Opts). subst_file(Src, Dest, Vars, Opts) -> {ok, Conts} = read_txt_file(Src), NConts = subst(Conts, Vars), write_file(Dest, NConts), case lists:member(preserve, Opts) of true -> {ok, FileInfo} = file:read_file_info(Src), file:write_file_info(Dest, FileInfo false -> ok end. %% subst(Str, Vars) %% Vars = [{Var, Val}] %% Var = Val = string() %% Substitute all occurrences of %Var% for Val in Str, using the list %% of variables in Vars. %% subst(Str, Vars) -> subst(Str, Vars, []). subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z -> subst_var([C| Rest], Vars, Result, [] subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z -> subst_var([C| Rest], Vars, Result, [] subst([$%, C| Rest], Vars, Result) when C == $_ -> subst_var([C| Rest], Vars, Result, [] subst([C| Rest], Vars, Result) -> subst(Rest, Vars, [C| Result] subst([], _Vars, Result) -> lists:reverse(Result). subst_var([$%| Rest], Vars, Result, VarAcc) -> Key = lists:reverse(VarAcc), case lists:keysearch(Key, 1, Vars) of {value, {Key, Value}} -> subst(Rest, Vars, lists:reverse(Value, Result) false -> subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]]) end; subst_var([C| Rest], Vars, Result, VarAcc) -> subst_var(Rest, Vars, Result, [C| VarAcc] subst_var([], Vars, Result, VarAcc) -> subst([], Vars, [VarAcc ++ [$%| Result]]). copy_file(Src, Dest) -> copy_file(Src, Dest, []). copy_file(Src, Dest, Opts) -> {ok,_} = file:copy(Src, Dest), case lists:member(preserve, Opts) of true -> {ok, FileInfo} = file:read_file_info(Src), file:write_file_info(Dest, FileInfo false -> ok end. write_file(FName, Conts) -> Enc = file:native_name_encoding(), {ok, Fd} = file:open(FName, [write]), file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)), file:close(Fd). read_txt_file(File) -> {ok, Bin} = file:read_file(File), {ok, binary_to_list(Bin)}. remove_dir_tree(Dir) -> remove_all_files(".", [Dir]). remove_all_files(Dir, Files) -> lists:foreach(fun(File) -> FilePath = filename:join([Dir, File]), case filelib:is_dir(FilePath) of true -> {ok, DirFiles} = file:list_dir(FilePath), remove_all_files(FilePath, DirFiles), file:del_dir(FilePath _ -> file:delete(FilePath) end end, Files).