fix deploy
This commit is contained in:
parent
29ff05684a
commit
46bf7e8fcc
@ -11,48 +11,7 @@
|
|||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([pull_image/1, check_image_exist/1]).
|
-export([pull_image/1, check_image_exist/1]).
|
||||||
-export([test/0]).
|
-export([create_container/2]).
|
||||||
|
|
||||||
|
|
||||||
test() ->
|
|
||||||
M = #{ <<"image">> => <<"nginx:latest">>,
|
|
||||||
<<"container_name">> => <<"my_nginx">>,
|
|
||||||
<<"command">> => [<<"nginx">>,<<"-g">>,<<"daemon off;">>],
|
|
||||||
<<"entrypoint">> => [<<"/docker-entrypoint.sh">>],
|
|
||||||
<<"environment">> => [<<"ENV1=val1">>, <<"ENV2=val2">>],
|
|
||||||
<<"env_file">> => [<<"./env.list">>],
|
|
||||||
<<"ports">> => [<<"8080:80">>, <<"443:443">>],
|
|
||||||
<<"expose">> => [<<"80">>, <<"443">>],
|
|
||||||
<<"volumes">> => [<<"/host/data:/data">>, <<"/host/log:/var/log">>],
|
|
||||||
<<"networks">> => [<<"mynet">>],
|
|
||||||
<<"labels">> => #{ <<"role">> => <<"web">>, <<"env">> => <<"prod">> },
|
|
||||||
<<"restart">> => <<"always">>,
|
|
||||||
<<"user">> => <<"www-data">>,
|
|
||||||
<<"working_dir">> => <<"/app">>,
|
|
||||||
<<"hostname">> => <<"myhost">>,
|
|
||||||
<<"privileged">> => true,
|
|
||||||
<<"cap_add">> => [<<"NET_ADMIN">>],
|
|
||||||
<<"cap_drop">> => [<<"MKNOD">>],
|
|
||||||
<<"devices">> => [<<"/dev/snd:/dev/snd">>],
|
|
||||||
<<"mem_limit">> => <<"512m">>,
|
|
||||||
<<"mem_reservation">> => <<"256m">>,
|
|
||||||
<<"cpu_shares">> => 512,
|
|
||||||
<<"cpus">> => 1.5,
|
|
||||||
<<"ulimits">> => #{ <<"nofile">> => <<"1024:2048">> },
|
|
||||||
<<"sysctls">> => #{ <<"net.ipv4.ip_forward">> => <<"1">> },
|
|
||||||
<<"tmpfs">> => [<<"/tmp">>],
|
|
||||||
<<"extra_hosts">> => [<<"host1:192.168.0.1">>],
|
|
||||||
<<"healthcheck">> => #{
|
|
||||||
%<<"test">> => [<<"CMD-SHELL">>, <<"curl -f http://localhost || exit 1">>],
|
|
||||||
<<"test">> => [<<"CMD">>, <<"ls">>, <<"-l">>],
|
|
||||||
<<"interval">> => <<"30s">>,
|
|
||||||
<<"timeout">> => <<"10s">>,
|
|
||||||
<<"retries">> => 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
L = json_to_create_args(M),
|
|
||||||
io:format("~p~n", [L]).
|
|
||||||
|
|
||||||
|
|
||||||
-spec pull_image(Image :: binary()) -> ok | {error, Reason :: any()}.
|
-spec pull_image(Image :: binary()) -> ok | {error, Reason :: any()}.
|
||||||
pull_image(Image) when is_binary(Image) ->
|
pull_image(Image) when is_binary(Image) ->
|
||||||
@ -75,6 +34,38 @@ pull_image(Image) when is_binary(Image) ->
|
|||||||
{error, <<"exec command startup failed">>}
|
{error, <<"exec command startup failed">>}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec create_container(ContainerDir :: string(), Config :: map()) -> ok | {error, Reason :: any()}.
|
||||||
|
create_container(ContainerDir, Config) when is_map(Config) ->
|
||||||
|
Image = maps:get(<<"image">>, Config),
|
||||||
|
Cmd = maps:get(<<"command">>, Config, []),
|
||||||
|
|
||||||
|
%% 挂载预留的目录,用来作为配置文件的存放
|
||||||
|
BinContainerDir = list_to_binary(ContainerDir),
|
||||||
|
BaseOptions = [<<"-v">>, <<BinContainerDir/binary, ":/etc/">>],
|
||||||
|
|
||||||
|
Options = build_options(Config),
|
||||||
|
Args = lists:flatten([Image | BaseOptions ++ Options ++ Cmd]),
|
||||||
|
CreateArgs = iolist_to_binary(lists:join(<<" ">>, Args)),
|
||||||
|
|
||||||
|
%% todo 重定向错误流 {stderr_to_stdout, true}
|
||||||
|
PortSettings = [stream, exit_status, use_stdio, binary],
|
||||||
|
ExecCmd = "docker create " ++ binary_to_list(CreateArgs),
|
||||||
|
lager:debug("create_container cmd : ~p", [ExecCmd]),
|
||||||
|
case catch erlang:open_port({spawn, ExecCmd}, PortSettings) of
|
||||||
|
Port when is_port(Port) ->
|
||||||
|
case gather_output(Port) of
|
||||||
|
{0, Output} ->
|
||||||
|
lager:debug("docker create output: ~p", [Output]),
|
||||||
|
ok;
|
||||||
|
{ExitCode, Error} ->
|
||||||
|
lager:debug("call me here: ~p", [Error]),
|
||||||
|
{error, {ExitCode, Error}}
|
||||||
|
end;
|
||||||
|
Error ->
|
||||||
|
lager:debug("error: ~p", [Error]),
|
||||||
|
{error, <<"exec command startup failed">>}
|
||||||
|
end.
|
||||||
|
|
||||||
-spec check_image_exist(Image :: binary()) -> boolean().
|
-spec check_image_exist(Image :: binary()) -> boolean().
|
||||||
check_image_exist(Image) when is_binary(Image) ->
|
check_image_exist(Image) when is_binary(Image) ->
|
||||||
PortSettings = [stream, exit_status, use_stdio, binary],
|
PortSettings = [stream, exit_status, use_stdio, binary],
|
||||||
@ -126,19 +117,6 @@ starts_with(Binary, Prefix) when is_binary(Binary), is_binary(Prefix) ->
|
|||||||
_ -> false
|
_ -> false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% json_to_create_args/1
|
|
||||||
%% 输入: map() JSON 配置
|
|
||||||
%% 输出: [docker create 参数列表],可以直接传给 open_port
|
|
||||||
%% 主函数
|
|
||||||
|
|
||||||
json_to_create_args(Config) when is_map(Config) ->
|
|
||||||
Image = maps:get(<<"image">>, Config),
|
|
||||||
Cmd = maps:get(<<"command">>, Config, []),
|
|
||||||
Options = build_options(Config),
|
|
||||||
Args = lists:flatten([Image | Options ++ Cmd]),
|
|
||||||
%lager:debug("args: ~p", [Args]),
|
|
||||||
iolist_to_binary(lists:join(<<" ">>, Args)).
|
|
||||||
|
|
||||||
%% 构建所有参数
|
%% 构建所有参数
|
||||||
build_options(Config) ->
|
build_options(Config) ->
|
||||||
lists:flatten([
|
lists:flatten([
|
||||||
@ -167,8 +145,6 @@ build_options(Config) ->
|
|||||||
build_healthcheck(Config)
|
build_healthcheck(Config)
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Helper 函数示例
|
|
||||||
|
|
||||||
build_name(Config) ->
|
build_name(Config) ->
|
||||||
case maps:get(<<"container_name">>, Config, undefined) of
|
case maps:get(<<"container_name">>, Config, undefined) of
|
||||||
undefined -> [];
|
undefined -> [];
|
||||||
|
|||||||
@ -13,10 +13,9 @@
|
|||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([start_link/4]).
|
-export([start_link/3]).
|
||||||
-export([deploy/1]).
|
-export([deploy/1]).
|
||||||
|
-export([test/0]).
|
||||||
-export([docker_pull/1, test/0]).
|
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
|
||||||
@ -31,7 +30,48 @@
|
|||||||
}).
|
}).
|
||||||
|
|
||||||
test() ->
|
test() ->
|
||||||
docker_pull(<<"docker.1ms.run/library/nginxyx">>).
|
M = #{
|
||||||
|
<<"image">> => <<"docker.1ms.run/library/nginx:latest">>,
|
||||||
|
<<"container_name">> => <<"my_nginx">>,
|
||||||
|
<<"command">> => [<<"nginx">>,<<"-g">>,<<"daemon off;">>],
|
||||||
|
<<"entrypoint">> => [<<"/docker-entrypoint.sh">>],
|
||||||
|
<<"environment">> => [<<"ENV1=val1">>, <<"ENV2=val2">>],
|
||||||
|
<<"env_file">> => [<<"./env.list">>],
|
||||||
|
<<"ports">> => [<<"8080:80">>, <<"443:443">>],
|
||||||
|
<<"expose">> => [<<"80">>, <<"443">>],
|
||||||
|
<<"volumes">> => [<<"/host/data:/data">>, <<"/host/log:/var/log">>],
|
||||||
|
<<"networks">> => [<<"mynet">>],
|
||||||
|
<<"labels">> => #{ <<"role">> => <<"web">>, <<"env">> => <<"prod">> },
|
||||||
|
<<"restart">> => <<"always">>,
|
||||||
|
<<"user">> => <<"www-data">>,
|
||||||
|
<<"working_dir">> => <<"/app">>,
|
||||||
|
<<"hostname">> => <<"myhost">>,
|
||||||
|
<<"privileged">> => true,
|
||||||
|
<<"cap_add">> => [<<"NET_ADMIN">>],
|
||||||
|
<<"cap_drop">> => [<<"MKNOD">>],
|
||||||
|
<<"devices">> => [<<"/dev/snd:/dev/snd">>],
|
||||||
|
<<"mem_limit">> => <<"512m">>,
|
||||||
|
<<"mem_reservation">> => <<"256m">>,
|
||||||
|
<<"cpu_shares">> => 512,
|
||||||
|
<<"cpus">> => 1.5,
|
||||||
|
<<"ulimits">> => #{ <<"nofile">> => <<"1024:2048">> },
|
||||||
|
<<"sysctls">> => #{ <<"net.ipv4.ip_forward">> => <<"1">> },
|
||||||
|
<<"tmpfs">> => [<<"/tmp">>],
|
||||||
|
<<"extra_hosts">> => [<<"host1:192.168.0.1">>],
|
||||||
|
<<"healthcheck">> => #{
|
||||||
|
%<<"test">> => [<<"CMD-SHELL">>, <<"curl -f http://localhost || exit 1">>],
|
||||||
|
<<"test">> => [<<"CMD">>, <<"ls">>, <<"-l">>],
|
||||||
|
<<"interval">> => <<"30s">>,
|
||||||
|
<<"timeout">> => <<"10s">>,
|
||||||
|
<<"retries">> => 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
TaskId = 1,
|
||||||
|
RootDir = "/tmp/",
|
||||||
|
|
||||||
|
Res = do_deploy(TaskId, RootDir, M),
|
||||||
|
lager:debug("res is: ~p", [Res]).
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% API
|
%%% API
|
||||||
@ -42,10 +82,10 @@ deploy(Pid) when is_pid(Pid) ->
|
|||||||
gen_server:cast(Pid, deploy).
|
gen_server:cast(Pid, deploy).
|
||||||
|
|
||||||
%% @doc Spawns the server and registers the local name (unique)
|
%% @doc Spawns the server and registers the local name (unique)
|
||||||
-spec(start_link(TaskId :: integer(), RootDir :: string(), ImageUrl :: binary(), Args :: map()) ->
|
-spec(start_link(TaskId :: integer(), RootDir :: string(), Config :: map()) ->
|
||||||
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
||||||
start_link(TaskId, RootDir, ImageUrl, Args) when is_integer(TaskId), is_list(RootDir), is_binary(ImageUrl), is_map(Args) ->
|
start_link(TaskId, RootDir, Config) when is_integer(TaskId), is_list(RootDir), is_map(Config) ->
|
||||||
gen_server:start_link(?MODULE, [TaskId, RootDir, ImageUrl, Args], []).
|
gen_server:start_link(?MODULE, [TaskId, RootDir, Config], []).
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% gen_server callbacks
|
%%% gen_server callbacks
|
||||||
@ -125,61 +165,30 @@ code_change(_OldVsn, State = #state{}, _Extra) ->
|
|||||||
% "command": ["nginx", "-g", "daemon off;"],
|
% "command": ["nginx", "-g", "daemon off;"],
|
||||||
% "restart": "always"
|
% "restart": "always"
|
||||||
%}
|
%}
|
||||||
-spec do_deploy(TaskId :: integer(), ServiceRootDir :: string(), ServiceId :: binary(), TarUrl :: binary()) -> no_return().
|
-spec do_deploy(TaskId :: integer(), RootDir :: string(), Config :: map()) -> ok | {error, Reason :: any()}.
|
||||||
do_deploy(TaskId, RootDir, Image, Args) when is_integer(TaskId), is_list(RootDir), is_binary(Image), is_map(Args) ->
|
do_deploy(TaskId, RootDir, Config) when is_integer(TaskId), is_list(RootDir), is_map(Config) ->
|
||||||
%% 尝试拉取镜像
|
%% 尝试拉取镜像
|
||||||
ContainerName = maps:get(<<"container_name">>, Args),
|
ContainerName = maps:get(<<"container_name">>, Config),
|
||||||
{ok, ContainerDir} = ensure_dirs(RootDir, ContainerName),
|
Image0 = maps:get(<<"image">>, Config),
|
||||||
|
Image = normalize_image(Image0),
|
||||||
|
|
||||||
|
{ok, ContainerDir} = ensure_dirs(RootDir, ContainerName),
|
||||||
case try_pull_image(Image) of
|
case try_pull_image(Image) of
|
||||||
ok ->
|
ok ->
|
||||||
%% 创建container
|
%% 创建container
|
||||||
|
|
||||||
%% 如果存在envs参数,则生成环境变量参数文件
|
%% 如果存在envs参数,则生成环境变量参数文件
|
||||||
maybe_create_env_file(ContainerDir, maps:get(<<"envs">>, Args, [])),
|
maybe_create_env_file(ContainerDir, maps:get(<<"envs">>, Config, [])),
|
||||||
|
|
||||||
ok;
|
%% 创建镜像, 并预留配置文件的绑定位置: "/etc/容器名称/"
|
||||||
|
case efka_docker_command:create_container(ContainerDir, Config) of
|
||||||
|
ok ->
|
||||||
|
ok;
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end;
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
end,
|
end.
|
||||||
|
|
||||||
%case download(binary_to_list(ImageUrl), RootDir) of
|
|
||||||
% {ok, TarFile, CostTs} ->
|
|
||||||
% Log = io_lib:format("download: ~p completed, cost time: ~p(ms)", [binary_to_list(TarUrl), CostTs]),
|
|
||||||
% efka_inetd_task_log:stash(TaskId, list_to_binary(Log)),
|
|
||||||
|
|
||||||
% %% 创建工作目录
|
|
||||||
% WorkDir = ServiceRootDir ++ "/work_dir/",
|
|
||||||
% case filelib:ensure_dir(WorkDir) of
|
|
||||||
% ok ->
|
|
||||||
% %% 清理目录下的文件
|
|
||||||
% catch delete_directory(WorkDir),
|
|
||||||
% case tar_extract(TarFile, WorkDir) of
|
|
||||||
% ok ->
|
|
||||||
% %% 更新数据
|
|
||||||
% ok = service_model:insert(#service{
|
|
||||||
% service_id = ServiceId,
|
|
||||||
% tar_url = TarUrl,
|
|
||||||
% %% 工作目录
|
|
||||||
% root_dir = ServiceRootDir,
|
|
||||||
% config_json = <<"">>,
|
|
||||||
% %% 状态: 0: 停止, 1: 运行中
|
|
||||||
% status = 0
|
|
||||||
% }),
|
|
||||||
% efka_inetd_task_log:stash(TaskId, <<"deploy success">>);
|
|
||||||
% {error, Reason} ->
|
|
||||||
% TarLog = io_lib:format("tar decompression: ~p, error: ~p", [filename:basename(TarFile), Reason]),
|
|
||||||
% efka_inetd_task_log:stash(TaskId, list_to_binary(TarLog))
|
|
||||||
% end;
|
|
||||||
% {error, Reason} ->
|
|
||||||
% DownloadLog = io_lib:format("make work_dir error: ~p", [Reason]),
|
|
||||||
% efka_inetd_task_log:stash(TaskId, list_to_binary(DownloadLog))
|
|
||||||
% end;
|
|
||||||
% {error, Reason} ->
|
|
||||||
% DownloadLog = io_lib:format("download: ~p, error: ~p", [binary_to_list(TarUrl), Reason]),
|
|
||||||
% efka_inetd_task_log:stash(TaskId, list_to_binary(DownloadLog))
|
|
||||||
%end.
|
|
||||||
ok.
|
|
||||||
|
|
||||||
-spec try_pull_image(Image :: binary()) -> ok | {error, Reason :: any()}.
|
-spec try_pull_image(Image :: binary()) -> ok | {error, Reason :: any()}.
|
||||||
try_pull_image(Image) when is_binary(Image) ->
|
try_pull_image(Image) when is_binary(Image) ->
|
||||||
@ -199,56 +208,12 @@ maybe_create_env_file(ContainerDir, Envs) when is_list(Envs)->
|
|||||||
ok = file:close(IoDevice),
|
ok = file:close(IoDevice),
|
||||||
{ok, TargetFile}.
|
{ok, TargetFile}.
|
||||||
|
|
||||||
docker_pull(ImageUrl) when is_binary(ImageUrl) ->
|
-spec ensure_dirs(RootDir :: string(), ContainerName :: binary()) -> {ok, ServerRootDir :: string()}.
|
||||||
%% todo 重定向错误流 {stderr_to_stdout, true}
|
ensure_dirs(RootDir, ContainerName) when is_list(RootDir), is_binary(ContainerName) ->
|
||||||
PortSettings = [stream, exit_status, use_stdio, binary],
|
%% 根目录
|
||||||
|
ServiceRootDir = RootDir ++ "/" ++ binary_to_list(ContainerName) ++ "/",
|
||||||
ImageUrl1 = normalize_image(ImageUrl),
|
ok = filelib:ensure_dir(ServiceRootDir),
|
||||||
ExecCmd = "docker pull " ++ binary_to_list(ImageUrl1),
|
{ok, ServiceRootDir}.
|
||||||
|
|
||||||
lager:debug("cmd : ~p", [ExecCmd]),
|
|
||||||
|
|
||||||
case catch erlang:open_port({spawn, ExecCmd}, PortSettings) of
|
|
||||||
Port when is_port(Port) ->
|
|
||||||
case gather_output(Port) of
|
|
||||||
{0, Output} ->
|
|
||||||
extract_sha256(Output);
|
|
||||||
{ExitCode, Error} ->
|
|
||||||
lager:debug("call me here: ~p", [Error]),
|
|
||||||
{error, {ExitCode, Error}}
|
|
||||||
end;
|
|
||||||
Error ->
|
|
||||||
lager:debug("error: ~p", [Error]),
|
|
||||||
{error, <<"exec command startup failed">>}
|
|
||||||
end.
|
|
||||||
|
|
||||||
gather_output(Port) ->
|
|
||||||
gather_output(Port, <<>>).
|
|
||||||
gather_output(Port, Acc) ->
|
|
||||||
receive
|
|
||||||
{Port, {data, Data}} ->
|
|
||||||
gather_output(Port, [Acc, Data]);
|
|
||||||
{Port, {exit_status, Status}} ->
|
|
||||||
{Status, iolist_to_binary(Acc)}
|
|
||||||
end.
|
|
||||||
|
|
||||||
extract_sha256(Output) when is_binary(Output) ->
|
|
||||||
Parts = binary:split(Output, <<$\n>>, [global]),
|
|
||||||
lager:debug("parts: ~p", [Parts]),
|
|
||||||
case lists:search(fun(Line) -> starts_with(Line, <<"Digest:">>) end, Parts) of
|
|
||||||
{value, Digest} ->
|
|
||||||
Sha256 = lists:last(binary:split(Digest, <<":">>, [global])),
|
|
||||||
{ok, Sha256};
|
|
||||||
false ->
|
|
||||||
error
|
|
||||||
end.
|
|
||||||
|
|
||||||
starts_with(Binary, Prefix) when is_binary(Binary), is_binary(Prefix) ->
|
|
||||||
PrefixSize = byte_size(Prefix),
|
|
||||||
case Binary of
|
|
||||||
<<Prefix:PrefixSize/binary, _Rest/binary>> -> true;
|
|
||||||
_ -> false
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec normalize_image(binary()) -> binary().
|
-spec normalize_image(binary()) -> binary().
|
||||||
normalize_image(Image) when is_binary(Image) ->
|
normalize_image(Image) when is_binary(Image) ->
|
||||||
@ -258,11 +223,4 @@ normalize_image(Image) when is_binary(Image) ->
|
|||||||
[_Name] -> <<Last/binary, ":latest">>;
|
[_Name] -> <<Last/binary, ":latest">>;
|
||||||
[_Name, _Tag] -> Last
|
[_Name, _Tag] -> Last
|
||||||
end,
|
end,
|
||||||
iolist_to_binary(lists:join(<<"/">>, PrefixParts ++ [NormalizedLast])).
|
iolist_to_binary(lists:join(<<"/">>, PrefixParts ++ [NormalizedLast])).
|
||||||
|
|
||||||
-spec ensure_dirs(RootDir :: string(), ContainerName :: binary()) -> {ok, ServerRootDir :: string()}.
|
|
||||||
ensure_dirs(RootDir, ContainerName) when is_list(RootDir), is_binary(ContainerName) ->
|
|
||||||
%% 根目录
|
|
||||||
ServiceRootDir = RootDir ++ "/" ++ binary_to_list(ContainerName) ++ "/",
|
|
||||||
ok = filelib:ensure_dir(ServiceRootDir),
|
|
||||||
{ok, ServiceRootDir}.
|
|
||||||
Loading…
x
Reference in New Issue
Block a user