写点什么

技术分享 | 如何让上千容器同时"存活"

作者:ShowMeBug
  • 2022 年 1 月 22 日
  • 本文字数:2275 字

    阅读完需:约 7 分钟

技术分享 | 如何让上千容器同时"存活"

最近接到一个需求,用户可以申请创建一个或者多个 docker 容器,容器要一直存在,用户不管过了多长时间都可以访问,而且用户产生的数据一直存在,换而言之就是要做到容器持久化,也就是说我们要提供一个小型的服务器,但是久而久之服务器资源就会被容器占满,就只能扩容了,我要做的就是在尽可能节省服务器的情况下,提供尽可能多的 docker 容器。



简要思路


在用户使用一段时间后,把用户产生的差异数据持久化,然后停止容器,当用户再次访问,通过拦截访问地址去新建容器并把持久化数据放到新的容器中,然后重定向,用户就可以访问到与之前相同的容器,仿佛容器一直存在。



实现流程


1.当用户请求接口生成一个容器,返回域名 2.一段时间后,停止容器,并配置下次访问启动容器 3.用户访问,通过之前的配置重启容器,重定向返回用户之前的数据具体如下图




工具支持


docker-client


因为我要用 java 操作 docker,启动、停止、删除、执行命令等操作,所以找了一个快捷使用的工具。


附上官网 https://github.com/docker-java/docker-java


kong api


因为在用户访问容器内接口的时候,容器如果已经删了,那就没有办法访问了,所以我在外层做了一层代理,用的工具是 kong API,kong 是基于 nginx 开发的 API Gateway,可以通过代码控制,而不像 nginx 那样去手动修改配置文件。


简单介绍一下我用到的几个功能:


Route:是请求的转发规则,按照 Hostname 和 PATH,将请求转发给 Service。(我理解的就是 nginx 的 location)


Services:是多个 Upstream 的集合,是 Route 的转发目标。(我理解的就是 nginx 的 server)


Plugin:是插件,plugin 可以是全局的,绑定到 Service,绑定到 Router,绑定到 Consumer。(有鉴权,访问限制,监控,日志记录等插件,我这里用的是 request-transformer,要在请求里携带一些参数用于重定向)


附上官网 https://docs.konghq.com/



具体实现一、生成容器


用户在访问接口的时候,后台创建并启动容器,并生成 kong 的配置,后续用户访问容器走的都是 kong 的代理,Route -> Services -> docker,设置心跳和失效时间,心跳是用户的操作产生的,失效时间是可以停止容器的时间,有心跳会更新失效时间。


代码示例


// 创建容器CreateContainerCmd createContainerCmd = dockerClient     .createContainerCmd(docker.getImage())     .withTty(true)     .withName(this.generateContainerName(docker))     .withCmd(managerUrl + docker.getId())     .withHostConfig(hostConfig);CreateContainerResponse containerResponse = createContainerCmd.exec();// 启动容器dockerClient.startContainerCmd(containerResponse.getId()).exec();
复制代码


二、停止容器


停止容器要保证下一次用户访问请求的时候容器可以再次启动,所以在停止容器后要把用户访问容器的请求,通过代理转到后台的接口,这里的代理 kong 。具体步骤就是后台会有一个 job 来控制让非活跃的容器持久化数据,然后停止容器,配置 kong,让下一次请求分发到后台服务。


具体实现:


1.增加 services 设置 http 接口请求地址


JKongAdmin admin = new JKongAdmin(kongAdminUrl);ServiceResp resp = admin.addService(new ServiceReq.Builder()                    .host(recoverHost)                    .port(recoverPort)                    .path(recoverPath)                    .build());
复制代码



2.增加 route 设置 serviceId, 用户访问域名


JKongAdmin admin = new JKongAdmin(kongAdminUrl);RouteResp resp = admin.addRoute(new RouteReq.Builder()                      .serviceId(serviceId)                      .host(host)                      .build());
复制代码



3.增加 plugin 设置 serviceId,后台地址 主要用于重启容器是带入参数


```javaJKongAdmin admin = new JKongAdmin(kongAdminUrl);Map<String, String> replaceConfigMap = new HashMap<>();replaceConfigMap.put("uri", recoverPath);PluginResp resp = admin.addPlugin(new PluginReq.Builder()                       .name("request-transformer")                       .serviceId(serviceId)                       .config("replace",replaceConfigMap)                       .enabled(true)                       .build());
复制代码



三、重启容器


用户通过域名访问到达 route,route 转发到对应的 service,在访问 service 配置的 http 接口,然后 http 接口启动容器,浏览器重定向,因为容器已经存在,只需要启动即可,速度可以达到秒级,以此达到容器“存活”的假象。



    @GetMapping("/recover")    @ApiOperation(value = "资源恢复")    public void recover(HttpServletRequest request, HttpServletResponse response) throws IOException {        String host = request.getHeader("x-forwarded-host");        String path = request.getHeader("x-forwarded-path");        this.playgroundService.recover(host);        response.sendRedirect(host + ":" + kongClientPort + path);    }
复制代码



总结


最后再分析一下需求,其实难点主要在容器停止之后,页面访问,再把容器启动,因为用户访问的是容器绑定的域名,没有 http 接口,所以只能用 kong API 拦截域名转发到后台服务器去启动 docker 和重定向,让空闲的容器停止,节省内存,需要的时候再启动,通过处理好像容器一直是启动状态,后台虽然转发的比较多,但是前台页面没有感知.


以上就是今天要讲的内容,技术上主要用到的是 kong API 和 docker-java,其实技术使用上没有什么难度,主要是分享思路。

用户头像

ShowMeBug

关注

技术评估与在线 Coding 面试工具 2020.05.19 加入

可记录、可分析、可复盘的技术评估与在线 Coding 面试工具:https://www.showmebug.com/

评论

发布
暂无评论
技术分享 | 如何让上千容器同时"存活"