非常教程

Erlang 20参考手册

指南:系统原则 | Guide: System principles

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

在使用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).
Erlang 20

Erlang 是一种通用的面向并发的编程语言,可应付大规模开发活动的程序设计语言和运行环境。

主页 https://www.erlang.org/
源码 https://github.com/erlang/otp
版本 20
发布版本 20.1