122 lines
5.9 KiB
Erlang
122 lines
5.9 KiB
Erlang
%%%-------------------------------------------------------------------
|
|
%%% @author anlicheng
|
|
%%% @copyright (C) 2025, <COMPANY>
|
|
%%% @doc
|
|
%%%
|
|
%%% @end
|
|
%%% Created : 07. 5月 2025 15:47
|
|
%%%-------------------------------------------------------------------
|
|
-module(docker_deployer).
|
|
-author("anlicheng").
|
|
-include("message_pb.hrl").
|
|
-dialyzer([{nowarn_function, normalize_image/1}]).
|
|
|
|
%% API
|
|
-export([start_monitor/3]).
|
|
-export([deploy/3]).
|
|
|
|
-define(TASK_SUCCESS, <<"success">>).
|
|
-define(TASK_FAIL, <<"fail">>).
|
|
|
|
%%%===================================================================
|
|
%%% API
|
|
%%%===================================================================
|
|
|
|
%% @doc Spawns the server and registers the local name (unique)
|
|
-spec(start_monitor(TaskId :: integer(), ContainerDir :: string(), Params :: message_pb:'ContainerDeployParams'()) ->
|
|
{ok, {Pid :: pid(), MRef :: reference()}}).
|
|
start_monitor(TaskId, ContainerDir, Params)
|
|
when is_integer(TaskId), is_list(ContainerDir), is_record(Params, 'ContainerDeployParams') ->
|
|
{ok, spawn_monitor(?MODULE, deploy, [TaskId, ContainerDir, Params])}.
|
|
|
|
%%%===================================================================
|
|
%%% 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 deploy(TaskId :: integer(), ContainerDir :: string(), Params :: message_pb:'ContainerDeployParams'()) -> no_return().
|
|
deploy(TaskId, ContainerDir, Params = #'ContainerDeployParams'{
|
|
container_name = ContainerName,
|
|
spec = #'ContainerSpec'{image = Image0}
|
|
}) when is_integer(TaskId), is_list(ContainerDir) ->
|
|
%% 尝试拉取镜像
|
|
trace_log(TaskId, <<"info">>, <<"开始部署容器:"/utf8, ContainerName/binary>>),
|
|
|
|
case docker_commands:check_container_exist(ContainerName) of
|
|
true ->
|
|
trace_log(TaskId, <<"info">>, <<"本地容器已经存在:"/utf8, ContainerName/binary>>),
|
|
efka_task_reporter:close(TaskId, ?TASK_FAIL);
|
|
false ->
|
|
Image = normalize_image(Image0),
|
|
|
|
trace_log(TaskId, <<"info">>, <<"使用镜像:"/utf8, Image/binary>>),
|
|
PullResult = case docker_commands:check_image_exist(Image) of
|
|
true ->
|
|
trace_log(TaskId, <<"info">>, <<"镜像本地已存在:"/utf8, Image/binary>>),
|
|
ok;
|
|
false ->
|
|
trace_log(TaskId, <<"info">>, <<"开始拉取镜像:"/utf8, Image/binary>>),
|
|
CB = fun
|
|
({message, M}) ->
|
|
trace_log(TaskId, <<"info">>, M);
|
|
({error, Error}) ->
|
|
trace_log(TaskId, <<"error">>, Error)
|
|
end,
|
|
docker_commands:pull_image(Image, CB)
|
|
end,
|
|
|
|
case PullResult of
|
|
ok ->
|
|
trace_log(TaskId, <<"info">>, <<"开始创建容器: "/utf8, ContainerName/binary>>),
|
|
case docker_commands:create_container(ContainerDir, Params) of
|
|
{ok, ContainerId} ->
|
|
%% 创建容器对应的配置文件
|
|
ConfigFile = docker_helper:get_config_file(ContainerDir),
|
|
case file:open(ConfigFile, [write, exclusive]) of
|
|
{ok, FD} ->
|
|
ok = file:write(FD, <<>>),
|
|
file:close(FD);
|
|
{error, Reason} ->
|
|
Reason1 = list_to_binary(io_lib:format("~p", [Reason])),
|
|
trace_log(TaskId, <<"notice">>, <<"创建配置文件失败: "/utf8, Reason1/binary>>)
|
|
end,
|
|
ShortContainerId = binary:part(ContainerId, 1, 12),
|
|
trace_log(TaskId, <<"info">>, <<"容器创建成功: "/utf8, ShortContainerId/binary>>),
|
|
trace_log(TaskId, <<"info">>, <<"任务完成"/utf8>>),
|
|
efka_task_reporter:close(TaskId, ?TASK_SUCCESS);
|
|
{error, Reason} ->
|
|
trace_log(TaskId, <<"error">>, <<"容器创建失败: "/utf8, Reason/binary>>),
|
|
trace_log(TaskId, <<"error">>, <<"任务失败"/utf8>>),
|
|
efka_task_reporter:close(TaskId, ?TASK_FAIL)
|
|
end;
|
|
{error, Reason} ->
|
|
trace_log(TaskId, <<"error">>, <<"镜像拉取失败: "/utf8, Reason/binary>>),
|
|
efka_task_reporter:close(TaskId, ?TASK_FAIL)
|
|
end
|
|
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 trace_log(TaskId :: integer(), Level :: binary(), Msg :: binary()) -> no_return().
|
|
trace_log(TaskId, Level, Msg) when is_integer(TaskId), is_binary(Level), is_binary(Msg) ->
|
|
efka_task_reporter:stream(TaskId, Level, Msg),
|
|
Info = iolist_to_binary([<<"task_id=">>, integer_to_binary(TaskId), <<" ">>, Level, <<" ">>, Msg]),
|
|
efka_logger:write(Info).
|