%%%------------------------------------------------------------------- %%% @author anlicheng %%% @copyright (C) 2025, %%% @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] -> <>; [_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).