fix docker command

This commit is contained in:
anlicheng 2025-09-16 11:40:25 +08:00
parent 0165e52ccb
commit 29ff05684a
6 changed files with 560 additions and 247 deletions

View File

@ -8,16 +8,16 @@
%%% @end %%% @end
%%% Created : 18. 4 2025 16:50 %%% Created : 18. 4 2025 16:50
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(efka_docker_container). -module(efka_container).
-author("anlicheng"). -author("anlicheng").
-include("efka_tables.hrl"). -include("efka_tables.hrl").
-behaviour(gen_server). -behaviour(gen_server).
%% API %% API
-export([start_link/2]). -export([start_link/3]).
-export([get_name/1, get_pid/1, attach_channel/2]). -export([get_name/1, get_pid/1, attach_channel/3]).
-export([push_config/3, request_config/1, invoke/3]). -export([push_envs/3, invoke/3]).
-export([metric_data/4, send_event/3]). -export([metric_data/4, send_event/3]).
%% gen_server callbacks %% gen_server callbacks
@ -28,6 +28,8 @@
container_id :: binary(), container_id :: binary(),
%% id信息 %% id信息
channel_pid :: pid() | undefined, channel_pid :: pid() | undefined,
%%
meta :: binary(),
%% port信息, OSPid = erlang:port_info(Port, os_pid) %% port信息, OSPid = erlang:port_info(Port, os_pid)
port :: undefined | port(), port :: undefined | port(),
%% pid %% pid
@ -44,26 +46,22 @@
%%% API %%% API
%%%=================================================================== %%%===================================================================
-spec get_name(ServiceId :: binary()) -> atom(). -spec get_name(ContainerId :: binary()) -> atom().
get_name(ServiceId) when is_binary(ServiceId) -> get_name(ContainerId) when is_binary(ContainerId) ->
list_to_atom("efka_service:" ++ binary_to_list(ServiceId)). list_to_atom("efka_container:" ++ binary_to_list(ContainerId)).
-spec get_pid(ServiceId :: binary()) -> undefined | pid(). -spec get_pid(ContainerId :: binary()) -> undefined | pid().
get_pid(ServiceId) when is_binary(ServiceId) -> get_pid(ContainerId) when is_binary(ContainerId) ->
whereis(get_name(ServiceId)). whereis(get_name(ContainerId)).
-spec push_config(Pid :: pid(), Ref :: reference(), ConfigJson :: binary()) -> no_return(). -spec push_envs(Pid :: pid(), Ref :: reference(), Envs :: binary()) -> no_return().
push_config(Pid, Ref, ConfigJson) when is_pid(Pid), is_binary(ConfigJson) -> push_envs(Pid, Ref, Envs) when is_pid(Pid), is_binary(Envs) ->
gen_server:cast(Pid, {push_config, Ref, self(), ConfigJson}). gen_server:cast(Pid, {push_envs, Ref, self(), Envs}).
-spec invoke(Pid :: pid(), Ref :: reference(), Payload :: binary()) -> no_return(). -spec invoke(Pid :: pid(), Ref :: reference(), Payload :: binary()) -> no_return().
invoke(Pid, Ref, Payload) when is_pid(Pid), is_reference(Ref), is_binary(Payload) -> invoke(Pid, Ref, Payload) when is_pid(Pid), is_reference(Ref), is_binary(Payload) ->
gen_server:cast(Pid, {invoke, Ref, self(), Payload}). gen_server:cast(Pid, {invoke, Ref, self(), Payload}).
-spec request_config(Pid :: pid()) -> {ok, Config :: binary()}.
request_config(Pid) when is_pid(Pid) ->
gen_server:call(Pid, request_config).
-spec metric_data(Pid :: pid(), DeviceUUID :: binary(), RouteKey :: binary(), Metric :: binary()) -> no_return(). -spec metric_data(Pid :: pid(), DeviceUUID :: binary(), RouteKey :: binary(), Metric :: binary()) -> no_return().
metric_data(Pid, DeviceUUID, RouteKey, Metric) when is_pid(Pid), is_binary(DeviceUUID), is_binary(RouteKey), is_binary(Metric) -> metric_data(Pid, DeviceUUID, RouteKey, Metric) when is_pid(Pid), is_binary(DeviceUUID), is_binary(RouteKey), is_binary(Metric) ->
gen_server:cast(Pid, {metric_data, DeviceUUID, RouteKey, Metric}). gen_server:cast(Pid, {metric_data, DeviceUUID, RouteKey, Metric}).
@ -72,15 +70,15 @@ metric_data(Pid, DeviceUUID, RouteKey, Metric) when is_pid(Pid), is_binary(Devic
send_event(Pid, EventType, Params) when is_pid(Pid), is_integer(EventType), is_binary(Params) -> send_event(Pid, EventType, Params) when is_pid(Pid), is_integer(EventType), is_binary(Params) ->
gen_server:cast(Pid, {send_event, EventType, Params}). gen_server:cast(Pid, {send_event, EventType, Params}).
-spec attach_channel(pid(), pid()) -> ok | {error, Reason :: binary()}. -spec attach_channel(Pid :: pid(), ChannelPid :: pid(), Meta :: binary()) -> ok | {error, Reason :: binary()}.
attach_channel(Pid, ChannelPid) when is_pid(Pid), is_pid(ChannelPid) -> attach_channel(Pid, ChannelPid, Meta) when is_pid(Pid), is_pid(ChannelPid), is_binary(Meta) ->
gen_server:call(Pid, {attach_channel, ChannelPid}). gen_server:call(Pid, {attach_channel, ChannelPid, Meta}).
%% @doc Spawns the server and registers the local name (unique) %% @doc Spawns the server and registers the local name (unique)
-spec(start_link(Name :: atom(), ContainerId :: binary()) -> -spec(start_link(Name :: atom(), ContainerId :: binary(), Args :: [binary()]) ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}). {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link(Name, ContainerId) when is_atom(Name), is_binary(ContainerId) -> start_link(Name, ContainerId, Args) when is_atom(Name), is_binary(ContainerId), is_list(Args) ->
gen_server:start_link({local, Name}, ?MODULE, [ContainerId], []). gen_server:start_link({local, Name}, ?MODULE, [ContainerId, Args], []).
%%%=================================================================== %%%===================================================================
%%% gen_server callbacks %%% gen_server callbacks
@ -91,11 +89,11 @@ start_link(Name, ContainerId) when is_atom(Name), is_binary(ContainerId) ->
-spec(init(Args :: term()) -> -spec(init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} | {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore). {stop, Reason :: term()} | ignore).
init([ContainerId]) -> init([ContainerId, Args]) ->
%% supervisor进程通过exit(ChildPid, shutdown)terminate函数被调用 %% supervisor进程通过exit(ChildPid, shutdown)terminate函数被调用
erlang:process_flag(trap_exit, true), erlang:process_flag(trap_exit, true),
case startup(<<>>, []) of case startup(<<>>, Args) of
{ok, Port} -> {ok, Port} ->
{os_pid, OSPid} = erlang:port_info(Port, os_pid), {os_pid, OSPid} = erlang:port_info(Port, os_pid),
lager:debug("[efka_service] service: ~p, port: ~p, boot_service success os_pid: ~p", [ContainerId, Port, OSPid]), lager:debug("[efka_service] service: ~p, port: ~p, boot_service success os_pid: ~p", [ContainerId, Port, OSPid]),
@ -116,12 +114,12 @@ init([ContainerId]) ->
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} | {stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}). {stop, Reason :: term(), NewState :: #state{}}).
%% channel %% channel
handle_call({attach_channel, ChannelPid}, _From, State = #state{channel_pid = OldChannelPid, container_id = ContainerId}) -> handle_call({attach_channel, ChannelPid, Meta}, _From, State = #state{channel_pid = OldChannelPid, container_id = ContainerId}) ->
case is_pid(OldChannelPid) andalso is_process_alive(OldChannelPid) of case is_pid(OldChannelPid) andalso is_process_alive(OldChannelPid) of
false -> false ->
erlang:monitor(process, ChannelPid), erlang:monitor(process, ChannelPid),
lager:debug("[efka_service] service_id: ~p, channel attched", [ContainerId]), lager:debug("[efka_service] service_id: ~p, channel attched", [ContainerId]),
{reply, ok, State#state{channel_pid = ChannelPid}}; {reply, ok, State#state{channel_pid = ChannelPid, meta = Meta}};
true -> true ->
{reply, {error, <<"channel exists">>}, State} {reply, {error, <<"channel exists">>}, State}
end; end;
@ -135,23 +133,25 @@ handle_call(_Request, _From, State = #state{}) ->
{noreply, NewState :: #state{}} | {noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} | {noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}). {stop, Reason :: term(), NewState :: #state{}}).
handle_cast({metric_data, DeviceUUID, RouteKey, Metric}, State = #state{service_id = ServiceId}) -> handle_cast({metric_data, DeviceUUID, RouteKey, Metric}, State = #state{container_id = ContainerId, meta = Meta}) ->
lager:debug("[efka_service] metric_data service_id: ~p, device_uuid: ~p, route_key: ~p, metric data: ~p", [ServiceId, DeviceUUID, RouteKey, Metric]), lager:debug("[efka_service] container_id: ~p, meta: ~p, device_uuid: ~p, route_key: ~p, metric data: ~p",
efka_remote_agent:metric_data(ServiceId, DeviceUUID, RouteKey, Metric), [ContainerId, Meta, DeviceUUID, RouteKey, Metric]),
%% meta相关的数据, todo container_id的数据是否需要加上
efka_remote_agent:metric_data(ContainerId, Meta, DeviceUUID, RouteKey, Metric),
{noreply, State}; {noreply, State};
handle_cast({send_event, EventType, Params}, State = #state{service_id = ServiceId}) -> handle_cast({send_event, EventType, Params}, State = #state{container_id = ContainerId, meta = Meta}) ->
efka_remote_agent:event(ServiceId, EventType, Params), efka_remote_agent:event(ContainerId, Meta, EventType, Params),
lager:debug("[efka_service] send_event, service_id: ~p, event_type: ~p, params: ~p", [ServiceId, EventType, Params]), lager:debug("[efka_service] send_event, container_id: ~p, meta: ~p, event_type: ~p, params: ~p", [ContainerId, Meta, EventType, Params]),
{noreply, State}; {noreply, State};
%% %%
handle_cast({push_config, Ref, ReceiverPid, ConfigJson}, State = #state{channel_pid = ChannelPid, service_id = ServiceId, inflight = Inflight, callbacks = Callbacks}) -> handle_cast({push_envs, Ref, ReceiverPid, Envs}, State = #state{channel_pid = ChannelPid, container_id = ContainerId, inflight = Inflight, callbacks = Callbacks}) ->
case is_pid(ChannelPid) andalso is_process_alive(ChannelPid) of case is_pid(ChannelPid) andalso is_process_alive(ChannelPid) of
true -> true ->
gen_channel:push_config(ChannelPid, Ref, self(), ConfigJson), gen_channel:push_config(ChannelPid, Ref, self(), Envs),
%% %%
CB = fun() -> service_model:set_config(ServiceId, ConfigJson) end, CB = fun() -> service_model:set_config(ContainerId, Envs) end,
{noreply, State#state{inflight = maps:put(Ref, ReceiverPid, Inflight), callbacks = maps:put(Ref, CB, Callbacks)}}; {noreply, State#state{inflight = maps:put(Ref, ReceiverPid, Inflight), callbacks = maps:put(Ref, CB, Callbacks)}};
false -> false ->
ReceiverPid ! {service_reply, Ref, {error, <<"channel is not alive">>}}, ReceiverPid ! {service_reply, Ref, {error, <<"channel is not alive">>}},

View File

@ -6,7 +6,7 @@
%%% @end %%% @end
%%% Created : 18. 4 2025 16:42 %%% Created : 18. 4 2025 16:42
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(efka_docker_container_sup). -module(efka_container_sup).
-author("anlicheng"). -author("anlicheng").
-include("efka_tables.hrl"). -include("efka_tables.hrl").
@ -14,7 +14,7 @@
%% API %% API
-export([start_link/0]). -export([start_link/0]).
-export([start_service/1, stop_service/1]). -export([start_container/1, stop_container/1]).
%% Supervisor callbacks %% Supervisor callbacks
-export([init/1]). -export([init/1]).
@ -42,11 +42,11 @@ start_link() ->
init([]) -> init([]) ->
SupFlags = #{strategy => one_for_one, intensity => 1000, period => 3600}, SupFlags = #{strategy => one_for_one, intensity => 1000, period => 3600},
%% %%
{ok, Services} = service_model:get_running_services(), %{ok, Services} = service_model:get_running_services(),
ServiceIds = lists:map(fun(#service{service_id = ServiceId}) -> ServiceId end, Services), %ServiceIds = lists:map(fun(#service{service_id = ServiceId}) -> ServiceId end, Services),
lager:debug("[efka_service_sup] will start services: ~p", [ServiceIds]), %lager:debug("[efka_service_sup] will start services: ~p", [ServiceIds]),
%Specs = lists:map(fun(ServiceId) -> child_spec(ServiceId) end, Services),
Specs = lists:map(fun(ServiceId) -> child_spec(ServiceId) end, Services), Specs = [],
{ok, {SupFlags, Specs}}. {ok, {SupFlags, Specs}}.
@ -54,9 +54,9 @@ init([]) ->
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
-spec start_service(ServiceId :: binary()) -> {ok, Pid :: pid()} | {error, Reason :: any()}. -spec start_container(ContainerId :: binary()) -> {ok, Pid :: pid()} | {error, Reason :: any()}.
start_service(ServiceId) when is_binary(ServiceId) -> start_container(ContainerId) when is_binary(ContainerId) ->
case supervisor:start_child(?MODULE, child_spec(ServiceId)) of case supervisor:start_child(?MODULE, child_spec(ContainerId)) of
{ok, Pid} when is_pid(Pid) -> {ok, Pid} when is_pid(Pid) ->
{ok, Pid}; {ok, Pid};
{error, {'already_started', Pid}} when is_pid(Pid) -> {error, {'already_started', Pid}} when is_pid(Pid) ->
@ -65,21 +65,21 @@ start_service(ServiceId) when is_binary(ServiceId) ->
{error, Error} {error, Error}
end. end.
-spec stop_service(ServiceId :: binary()) -> ok. -spec stop_container(ContainerId :: binary()) -> ok.
stop_service(ServiceId) when is_binary(ServiceId) -> stop_container(ContainerId) when is_binary(ContainerId) ->
ChildId = efka_service:get_name(ServiceId), ChildId = efka_container:get_name(ContainerId),
supervisor:terminate_child(?MODULE, ChildId), supervisor:terminate_child(?MODULE, ChildId),
supervisor:delete_child(?MODULE, ChildId). supervisor:delete_child(?MODULE, ChildId).
child_spec(#service{service_id = ServiceId}) when is_binary(ServiceId) -> child_spec(#service{service_id = ServiceId}) when is_binary(ServiceId) ->
child_spec(ServiceId); child_spec(ServiceId);
child_spec(ServiceId) when is_binary(ServiceId) -> child_spec(ContainerId) when is_binary(ContainerId) ->
Name = efka_service:get_name(ServiceId), Name = efka_service:get_name(ContainerId),
#{ #{
id => Name, id => Name,
start => {efka_service, start_link, [Name, ServiceId]}, start => {efka_container, start_link, [Name, ContainerId]},
restart => permanent, restart => permanent,
shutdown => 5000, shutdown => 5000,
type => worker, type => worker,
modules => ['efka_service'] modules => ['efka_container']
}. }.

View File

@ -0,0 +1,325 @@
%%%-------------------------------------------------------------------
%%% @author anlicheng
%%% @copyright (C) 2025, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 15. 9 2025 16:11
%%%-------------------------------------------------------------------
-module(efka_docker_command).
-author("anlicheng").
%% API
-export([pull_image/1, check_image_exist/1]).
-export([test/0]).
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()}.
pull_image(Image) when is_binary(Image) ->
%% todo {stderr_to_stdout, true}
PortSettings = [stream, exit_status, use_stdio, binary],
ExecCmd = "docker pull " ++ binary_to_list(Image),
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} ->
lager:debug("docker pull 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().
check_image_exist(Image) when is_binary(Image) ->
PortSettings = [stream, exit_status, use_stdio, binary],
ExecCmd = "docker images -q " ++ binary_to_list(Image),
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, <<>>} ->
false;
{0, <<_:12/binary, $\n>>} ->
true;
{_ExitCode, _Error} ->
false
end;
_Error ->
false
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
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.
%% 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) ->
lists:flatten([
build_name(Config),
build_entrypoint(Config),
build_ports(Config),
build_expose(Config),
build_volumes(Config),
build_env(Config),
build_env_file(Config),
build_networks(Config),
build_labels(Config),
build_restart(Config),
build_user(Config),
build_working_dir(Config),
build_hostname(Config),
build_privileged(Config),
build_cap_add_drop(Config),
build_devices(Config),
build_memory(Config),
build_cpu(Config),
build_ulimits(Config),
build_sysctls(Config),
build_tmpfs(Config),
build_extra_hosts(Config),
build_healthcheck(Config)
]).
%% Helper
build_name(Config) ->
case maps:get(<<"container_name">>, Config, undefined) of
undefined -> [];
Name -> [<<"--name">>, Name]
end.
build_entrypoint(Config) ->
case maps:get(<<"entrypoint">>, Config, []) of
[] -> [];
EP -> [<<"--entrypoint">> | EP]
end.
build_ports(Config) ->
Ports = maps:get(<<"ports">>, Config, []),
lists:map(fun(P) -> [<<"-p">>, P] end, Ports).
build_expose(Config) ->
Ports = maps:get(<<"expose">>, Config, []),
lists:map(fun(P) -> [<<"--expose">>, P] end, Ports).
build_volumes(Config) ->
Vols = maps:get(<<"volumes">>, Config, []),
lists:map(fun(V) -> [<<"-v">>, V] end, Vols).
build_env(Config) ->
Envs = maps:get(<<"environment">>, Config, []),
lists:map(fun(E) -> [<<"-e">>, E] end, Envs).
build_env_file(Config) ->
Files = maps:get(<<"env_file">>, Config, []),
lists:map(fun(F) -> [<<"--env-file">>, F] end, Files).
build_networks(Config) ->
Nets = maps:get(<<"networks">>, Config, []),
lists:map(fun(Net) -> [<<"--network">>, Net] end, Nets).
build_labels(Config) ->
case maps:get(<<"labels">>, Config, #{}) of
#{} ->
[];
Labels ->
lists:map(fun({K, V}) -> [<<"--label">>, <<K/binary, "=", V/binary>>] end, maps:to_list(Labels))
end.
build_restart(Config) ->
case maps:get(<<"restart">>, Config, undefined) of
undefined -> [];
Policy -> [<<"--restart">>, Policy]
end.
build_user(Config) ->
case maps:get(<<"user">>, Config, undefined) of
undefined -> [];
U -> [<<"--user">>, U]
end.
build_working_dir(Config) ->
case maps:get(<<"working_dir">>, Config, undefined) of
undefined -> [];
D -> [<<"--workdir">>, D]
end.
build_hostname(Config) ->
case maps:get(<<"hostname">>, Config, undefined) of
undefined -> [];
H -> [<<"--hostname">>, H]
end.
build_privileged(Config) ->
case maps:get(<<"privileged">>, Config, false) of
true -> [<<"--privileged">>];
_ -> []
end.
build_cap_add_drop(Config) ->
Add = maps:get(<<"cap_add">>, Config, []),
Drop = maps:get(<<"cap_drop">>, Config, []),
lists:map(fun(C) -> [<<"--cap-add">>, C] end, Add) ++ lists:map(fun(C0) -> [<<"--cap-drop">>, C0] end, Drop).
build_devices(Config) ->
Devs = maps:get(<<"devices">>, Config, []),
lists:map(fun(D) -> [<<"--device">>, D] end, Devs).
build_memory(Config) ->
Mem = maps:get(<<"mem_limit">>, Config, undefined),
MemRes = maps:get(<<"mem_reservation">>, Config, undefined),
Res1 = if Mem /= undefined -> [<<"--memory">>, Mem]; true -> [] end,
Res2 = if MemRes /= undefined -> [<<"--memory-reservation">>, MemRes]; true -> [] end,
Res1 ++ Res2.
build_cpu(Config) ->
CPU = maps:get(<<"cpus">>, Config, undefined),
Shares = maps:get(<<"cpu_shares">>, Config, undefined),
Res1 = if
CPU /= undefined ->
Bin = iolist_to_binary(io_lib:format("~p", [CPU])),
[<<"--cpus">>, Bin];
true ->
[]
end,
Res2 = if
Shares /= undefined ->
Bin1 = iolist_to_binary(io_lib:format("~p", [Shares])),
[<<"--cpu-shares">>, Bin1];
true ->
[]
end,
Res1 ++ Res2.
build_ulimits(Config) ->
UL = maps:get(<<"ulimits">>, Config, #{}),
lists:map(fun({K, V}) -> [<<"--ulimit">>, <<K/binary, "=", V/binary>>] end, maps:to_list(UL)).
build_sysctls(Config) ->
SC = maps:get(<<"sysctls">>, Config, #{}),
lists:map(fun({K, V}) -> [<<"--sysctl ">>, <<K/binary, "=", V/binary>>] end, maps:to_list(SC)).
build_tmpfs(Config) ->
Tmp = maps:get(<<"tmpfs">>, Config, []),
lists:map(fun(T) -> [<<"--tmpfs">>, T] end, Tmp).
build_extra_hosts(Config) ->
Hosts = maps:get(<<"extra_hosts">>, Config, []),
lists:map(fun(H) -> [<<"--add-host">>, H] end, Hosts).
build_healthcheck(Config) ->
HC = maps:get(<<"healthcheck">>, Config, #{}),
lists:map(fun({K, V}) ->
case K of
<<"test">> ->
case V of
%% Test ["CMD-SHELL", Cmd]
[<<"CMD-SHELL">>, Cmd] ->
[<<"--health-cmd">>, <<$", Cmd/binary, $">>];
%% Test ["CMD", Arg1, Arg2...]
[<<"CMD">> | CmdList] ->
CmdArgs = iolist_to_binary(lists:join(<<" ">>, CmdList)),
[<<"--health-cmd">>, <<$", CmdArgs/binary, $">>];
%% Test <<"NONE">>
[<<"NONE">>] ->
[<<"--no-healthcheck">>];
_ ->
[]
end;
<<"interval">> ->
[<<"--health-interval">>, V];
<<"timeout">> ->
[<<"--health-timeout">>, V];
<<"retries">> ->
[<<"--health-retries">>, io_lib:format("~p", [V])];
_ ->
[]
end
end, maps:to_list(HC)).

View File

@ -33,9 +33,9 @@
%%% API %%% API
%%%=================================================================== %%%===================================================================
-spec deploy(TaskId :: integer(), ServerId :: binary(), TarUrl :: binary()) -> ok | {error, Reason :: binary()}. -spec deploy(TaskId :: integer(), ImageUrl :: binary(), Args :: [binary()]) -> {ok, ContainerId :: binary()} | {error, Reason :: binary()}.
deploy(TaskId, ServerId, TarUrl) when is_integer(TaskId), is_binary(ServerId), is_binary(TarUrl) -> deploy(TaskId, ImageUrl, Args) when is_integer(TaskId), is_binary(ImageUrl), is_list(Args) ->
gen_server:call(?SERVER, {deploy, TaskId, ServerId, TarUrl}). gen_server:call(?SERVER, {deploy, TaskId, ImageUrl, Args}).
-spec start_service(ServiceId :: binary()) -> ok | {error, Reason :: term()}. -spec start_service(ServiceId :: binary()) -> ok | {error, Reason :: term()}.
start_service(ServiceId) when is_binary(ServiceId) -> start_service(ServiceId) when is_binary(ServiceId) ->
@ -75,53 +75,39 @@ init([]) ->
{noreply, NewState :: #state{}, timeout() | hibernate} | {noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} | {stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}). {stop, Reason :: term(), NewState :: #state{}}).
handle_call({deploy, TaskId, ServiceId, TarUrl}, _From, State = #state{root_dir = RootDir, task_map = TaskMap}) -> handle_call({deploy, TaskId, ImageUrl, Args}, _From, State = #state{root_dir = RootDir, task_map = TaskMap}) ->
%% %%
{ok, ServiceRootDir} = ensure_dirs(RootDir, ServiceId), {ok, TaskPid} = efka_inetd_task:start_link(TaskId, RootDir, ImageUrl, Args),
ServicePid = efka_service:get_pid(ServiceId),
case is_pid(ServicePid) of
true ->
{reply, {error, <<"the service is running, stop first">>}, State};
false ->
case check_download_url(TarUrl) of
ok ->
{ok, TaskPid} = efka_inetd_task:start_link(TaskId, ServiceRootDir, ServiceId, TarUrl),
efka_inetd_task:deploy(TaskPid), efka_inetd_task:deploy(TaskPid),
lager:debug("[efka_inetd] start task_id: ~p, tar_url: ~p", [TaskId, TarUrl]), lager:debug("[efka_inetd] start task_id: ~p, tar_url: ~p", [TaskId, ImageUrl]),
%% todo
{reply, ok, State#state{task_map = maps:put(TaskPid, {TaskId, ServiceId}, TaskMap)}}; {reply, ok, State#state{task_map = maps:put(TaskPid, {TaskId, xx}, TaskMap)}};
{error, Reason} ->
lager:debug("[efka_inetd] check_download_url: ~p, get error: ~p", [TarUrl, Reason]),
{reply, {error, <<"download url error">>}, State}
end
end;
%% : %% :
handle_call({start_service, ServiceId}, _From, State) -> handle_call({start_container, ContainerId}, _From, State) ->
case efka_service:get_pid(ServiceId) of case efka_container:get_pid(ContainerId) of
undefined -> undefined ->
case efka_service_sup:start_service(ServiceId) of case efka_container_sup:start_container(ContainerId) of
{ok, _} -> {ok, _} ->
%% , efka重启的时候 %% , efka重启的时候
ok = service_model:change_status(ServiceId, 1), ok = service_model:change_status(ContainerId, 1),
{reply, ok, State}; {reply, ok, State};
{error, Reason} -> {error, Reason} ->
{reply, {error, Reason}, State} {reply, {error, Reason}, State}
end; end;
ServicePid when is_pid(ServicePid) -> ContainerPid when is_pid(ContainerPid) ->
{reply, {error, <<"service is running">>}, State} {reply, {error, <<"service is running">>}, State}
end; end;
%% , status字段 %% , status字段
handle_call({stop_service, ServiceId}, _From, State = #state{}) -> handle_call({stop_container, ContainerId}, _From, State = #state{}) ->
case efka_service:get_pid(ServiceId) of case efka_container:get_pid(ContainerId) of
undefined -> undefined ->
{reply, {error, <<"service not running">>}, State}; {reply, {error, <<"service not running">>}, State};
ServicePid when is_pid(ServicePid) -> ContainerPid when is_pid(ContainerPid) ->
efka_service_sup:stop_service(ServiceId), efka_container_sup:stop_container(ContainerPid),
%% , efka重启的时候 %% , efka重启的时候
ok = service_model:change_status(ServiceId, 0), ok = service_model:change_status(ContainerId, 0),
{reply, ok, State} {reply, ok, State}
end; end;
@ -184,28 +170,3 @@ code_change(_OldVsn, State = #state{}, _Extra) ->
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
-spec ensure_dirs(RootDir :: string(), ServerId :: binary()) -> {ok, ServerRootDir :: string()}.
ensure_dirs(RootDir, ServerId) when is_list(RootDir), is_binary(ServerId) ->
%%
ServiceRootDir = RootDir ++ "/" ++ binary_to_list(ServerId) ++ "/",
ok = filelib:ensure_dir(ServiceRootDir),
{ok, ServiceRootDir}.
%% head请求先判定下载地址是否正确
-spec check_download_url(Url :: string() | binary()) -> ok | {error, Reason :: term()}.
check_download_url(Url) when is_binary(Url) ->
check_download_url(binary_to_list(Url));
check_download_url(Url) when is_list(Url) ->
SslOpts = [
{ssl, [
%
{verify, verify_none}
]}
],
case httpc:request(head, {Url, []}, SslOpts, [{sync, true}]) of
{ok, {{_, 200, "OK"}, _Headers, _}} ->
ok;
{error, Reason} ->
{error, Reason}
end.

View File

@ -16,18 +16,23 @@
-export([start_link/4]). -export([start_link/4]).
-export([deploy/1]). -export([deploy/1]).
-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]).
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
-record(state, { -record(state, {
service_root_dir :: string(), root_dir :: string(),
task_id :: integer(), task_id :: integer(),
service_id :: binary(), image_url :: binary(),
tar_url :: binary() args = [] :: list()
}). }).
test() ->
docker_pull(<<"docker.1ms.run/library/nginxyx">>).
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
@ -37,10 +42,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(), ServiceRootDir :: string(), ServiceId :: binary(), TarUrl :: binary()) -> -spec(start_link(TaskId :: integer(), RootDir :: string(), ImageUrl :: binary(), Args :: map()) ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}). {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link(TaskId, ServiceRootDir, ServiceId, TarUrl) when is_integer(TaskId), is_list(ServiceRootDir), is_binary(ServiceId), is_binary(TarUrl) -> start_link(TaskId, RootDir, ImageUrl, Args) when is_integer(TaskId), is_list(RootDir), is_binary(ImageUrl), is_map(Args) ->
gen_server:start_link(?MODULE, [TaskId, ServiceRootDir, ServiceId, TarUrl], []). gen_server:start_link(?MODULE, [TaskId, RootDir, ImageUrl, Args], []).
%%%=================================================================== %%%===================================================================
%%% gen_server callbacks %%% gen_server callbacks
@ -51,8 +56,8 @@ start_link(TaskId, ServiceRootDir, ServiceId, TarUrl) when is_integer(TaskId), i
-spec(init(Args :: term()) -> -spec(init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} | {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore). {stop, Reason :: term()} | ignore).
init([TaskId, ServiceRootDir, ServiceId, TarUrl]) -> init([TaskId, RootDir, ImageUrl, Args]) ->
{ok, #state{task_id = TaskId, service_root_dir = ServiceRootDir, service_id = ServiceId, tar_url = TarUrl}}. {ok, #state{task_id = TaskId, root_dir = RootDir, image_url = ImageUrl, args = Args}}.
%% @private %% @private
%% @doc Handling call messages %% @doc Handling call messages
@ -73,8 +78,8 @@ handle_call(_Request, _From, State = #state{}) ->
{noreply, NewState :: #state{}} | {noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} | {noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}). {stop, Reason :: term(), NewState :: #state{}}).
handle_cast(deploy, State = #state{task_id = TaskId, service_root_dir = ServiceRootDir, service_id = ServiceId, tar_url = TarUrl}) -> handle_cast(deploy, State = #state{task_id = TaskId, root_dir = RootDir, image_url = ImageUrl, args = Args}) ->
do_deploy(TaskId, ServiceRootDir, ServiceId, TarUrl), do_deploy(TaskId, RootDir, ImageUrl, Args),
{stop, normal, State}; {stop, normal, State};
handle_cast(_Request, State) -> handle_cast(_Request, State) ->
{stop, normal, State}. {stop, normal, State}.
@ -110,132 +115,154 @@ code_change(_OldVsn, State = #state{}, _Extra) ->
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
%{
% "image": "nginx:latest",
% "container_name": "my_nginx",
% "ports": ["8080:80", "443:443"],
% "volumes": ["/host/data:/data", "/host/log:/var/log"],
% "envs": ["ENV1=val1", "ENV2=val2"],
% "entrypoint": ["/docker-entrypoint.sh"],
% "command": ["nginx", "-g", "daemon off;"],
% "restart": "always"
%}
-spec do_deploy(TaskId :: integer(), ServiceRootDir :: string(), ServiceId :: binary(), TarUrl :: binary()) -> no_return(). -spec do_deploy(TaskId :: integer(), ServiceRootDir :: string(), ServiceId :: binary(), TarUrl :: binary()) -> no_return().
do_deploy(TaskId, ServiceRootDir, ServiceId, TarUrl) when is_integer(TaskId), is_list(ServiceRootDir), is_binary(ServiceId), is_binary(TarUrl) -> do_deploy(TaskId, RootDir, Image, Args) when is_integer(TaskId), is_list(RootDir), is_binary(Image), is_map(Args) ->
case download(binary_to_list(TarUrl), ServiceRootDir) of %%
{ok, TarFile, CostTs} -> ContainerName = maps:get(<<"container_name">>, Args),
Log = io_lib:format("download: ~p completed, cost time: ~p(ms)", [binary_to_list(TarUrl), CostTs]), {ok, ContainerDir} = ensure_dirs(RootDir, ContainerName),
efka_inetd_task_log:stash(TaskId, list_to_binary(Log)),
%% case try_pull_image(Image) of
WorkDir = ServiceRootDir ++ "/work_dir/",
case filelib:ensure_dir(WorkDir) of
ok -> ok ->
%% %% container
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.
%% %% envs参数
-spec delete_directory(string()) -> ok | {error, term()}. maybe_create_env_file(ContainerDir, maps:get(<<"envs">>, Args, [])),
delete_directory(Dir) when is_list(Dir) ->
% ok;
case file:list_dir(Dir) of {error, Reason} ->
{ok, Files} -> {error, Reason}
lists:foreach(fun(File) -> end,
FullPath = filename:join(Dir, File),
case filelib:is_dir(FullPath) of %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()}.
try_pull_image(Image) when is_binary(Image) ->
case efka_docker_command:check_image_exist(Image) of
true -> true ->
delete_directory(FullPath); ok;
false -> false ->
file:delete(FullPath) efka_docker_command:pull_image(Image)
end
end, Files),
%
file:del_dir(Dir);
{error, enoent} ->
ok;
{error, Reason} ->
{error, Reason}
end. end.
%% maybe_create_env_file(_ContainerDir, []) ->
-spec tar_extract(string(), string()) -> ok | {error, term()}. ok;
tar_extract(TarFile, TargetDir) when is_list(TarFile), is_list(TargetDir) -> maybe_create_env_file(ContainerDir, Envs) when is_list(Envs)->
%% , options: verbose TargetFile = ContainerDir ++ "env",
erl_tar:extract(TarFile, [compressed, {cwd, TargetDir}]). {ok, IoDevice} = file:open(TargetFile, [write, binary]),
lists:foreach(fun(Env) -> file:write(IoDevice, <<Env/binary, $\n>>) end, Envs),
ok = file:close(IoDevice),
{ok, TargetFile}.
%% docker_pull(ImageUrl) when is_binary(ImageUrl) ->
-spec download(Url :: string(), TargetDir :: string()) -> %% todo {stderr_to_stdout, true}
{ok, TarFile :: string(), CostTs :: integer()} | {error, Reason :: term()}. PortSettings = [stream, exit_status, use_stdio, binary],
download(Url, TargetDir) when is_list(Url), is_list(TargetDir) ->
SslOpts = [
{ssl, [
%
{verify, verify_none}
]}
],
TargetFile = get_filename_from_url(Url), ImageUrl1 = normalize_image(ImageUrl),
FullFilename = TargetDir ++ TargetFile, ExecCmd = "docker pull " ++ binary_to_list(ImageUrl1),
StartTs = os:timestamp(), lager:debug("cmd : ~p", [ExecCmd]),
case httpc:request(get, {Url, []}, SslOpts, [{sync, false}, {stream, self}]) of
{ok, RequestId} -> case catch erlang:open_port({spawn, ExecCmd}, PortSettings) of
case receive_data(RequestId, FullFilename) of Port when is_port(Port) ->
ok -> case gather_output(Port) of
EndTs = os:timestamp(), {0, Output} ->
%% extract_sha256(Output);
CostMs = timer:now_diff(EndTs, StartTs) div 1000, {ExitCode, Error} ->
{ok, FullFilename, CostMs}; lager:debug("call me here: ~p", [Error]),
{error, Reason} -> {error, {ExitCode, Error}}
%%
file:delete(FullFilename),
{error, Reason}
end; end;
{error, Reason} -> Error ->
{error, Reason} lager:debug("error: ~p", [Error]),
{error, <<"exec command startup failed">>}
end. end.
%% , gather_output(Port) ->
receive_data(RequestId, FullFilename) -> gather_output(Port, <<>>).
gather_output(Port, Acc) ->
receive receive
{http, {RequestId, stream_start, _Headers}} -> {Port, {data, Data}} ->
{ok, File} = file:open(FullFilename, [write, binary]), gather_output(Port, [Acc, Data]);
receive_data0(RequestId, File); {Port, {exit_status, Status}} ->
{http, {RequestId, {{_, 404, Status}, _Headers, Body}}} -> {Status, iolist_to_binary(Acc)}
lager:debug("[efka_downloader] http_status: ~p, body: ~p", [Status, Body]),
{error, Status}
end.
%%
receive_data0(RequestId, File) ->
receive
{http, {RequestId, {error, Reason}}} ->
ok = file:close(File),
{error, Reason};
{http, {RequestId, stream_end, _Headers}} ->
ok = file:close(File),
ok;
{http, {RequestId, stream, Data}} ->
file:write(File, Data),
receive_data0(RequestId, File)
end. end.
-spec get_filename_from_url(Url :: string()) -> string(). extract_sha256(Output) when is_binary(Output) ->
get_filename_from_url(Url) when is_list(Url) -> Parts = binary:split(Output, <<$\n>>, [global]),
URIMap = uri_string:parse(Url), lager:debug("parts: ~p", [Parts]),
Path = maps:get(path, URIMap), case lists:search(fun(Line) -> starts_with(Line, <<"Digest:">>) end, Parts) of
filename:basename(Path). {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().
normalize_image(Image) when is_binary(Image) ->
Parts = binary:split(Image, <<"/">>, [global]),
{PrefixParts, [Last]} = lists:split(length(Parts) - 1, Parts),
NormalizedLast = case binary:split(Last, <<":">>, [global]) of
[_Name] -> <<Last/binary, ":latest">>;
[_Name, _Tag] -> Last
end,
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}.

View File

@ -64,14 +64,14 @@ init([]) ->
modules => ['efka_inetd'] modules => ['efka_inetd']
}, },
#{ %#{
id => 'efka_remote_agent', % id => 'efka_remote_agent',
start => {'efka_remote_agent', start_link, []}, % start => {'efka_remote_agent', start_link, []},
restart => permanent, % restart => permanent,
shutdown => 2000, % shutdown => 2000,
type => worker, % type => worker,
modules => ['efka_remote_agent'] % modules => ['efka_remote_agent']
}, %},
#{ #{
id => 'tcp_sup', id => 'tcp_sup',