暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

使用Docker Remote API查询容器状态以及进行交互操作

雨天做梦 2022-11-11
1229

0 缘起

上次使用Expect+SSH实现特定容器的重启操作,但是这一次需要查询容器状态,展示在页面上,然后还要通过按钮实现容器的start/stop/restart操作,这每一个容器每一个指令都这么来一遍吧,虽说也可以,但是不够优雅,所以经过我的不断搜索,我找到了一个更优雅的办法——Dockers Remote API。哦绝对不是我没试出来Portainer API才退而求其次用Dockers Remote API的,也不是因为有Dockers Remote API的视频教程。

1 Docker Remote API

首先,要设置Docker API的端口,编辑

    /usr/lib/systemd/system/docker.service

    文件中,

      ExecStart=/usr/bin/dockerd

      一行的末尾添加

        -H tcp://0.0.0.0:<port>

        可以选择一个无冲突的端口号作为Docker API的端口,保存后记得重启Docker守护进程和docker。

          $ systemctl daemon-reload
          $ systemctl restart docker

          当我们完成后,在服务器上就可以通过

            $ curl localhost:<port>/containers/json

            来获取运行中容器的信息了。

            当然,这只是完成了在本机上,或者是局域网内通过终端能够获取运行中容器的信息,距离真正利用上Docker Remote API还有一段距离,更别提通过按钮和网页进行交互了。

            2 CORS跨域问题

            同源策略是一个浏览器重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

            浏览器的同源策略,同源——即“协议protocol/主机host/端口port”这个元组都相同的话,浏览器才允许交互,而显而易见的是,处于宿主机的Docker API的端口,和容器中的WebUI服务端口,是不一样的……所以如何解决跨域问题……当然正确的做法是修改你的服务器端,添加

              Access-Control-Allow-Origin

              标头,指定为*,或者发起请求的那个就行了。但是……Docker API你让我去哪里找这个啊……

              拿一个MDN给出例子来说的话:

              下表给出了与 URL http://store.company.com/dir/page.html 的源进行对比的示例:

              URL
              结果原因
              http://store.company.com/dir2/other.htm 同源只有路径不同
              http://store.company.com/dir/inner/another.html同源只有路径不同
              https://store.company.com/secure.html失败协议不同
              http://store.company.com:81/dir/etc.html失败端口不同 ( http:// 默认口是80)
              http://news.company.com/dir/other.html
              失败主机不同

              网上给出了许多方案,比如用Nginx反向代理把请求转发,当然也可以,就是不够优(jian)雅(dan),Nginx容器又需要部署,又需要设置,还是挺麻烦的。这不我找到了一个CORS—anywhere的简单方便的方法,只要按着github上面的,把仓库拉取到本地,进入文件夹运行npm install,前提是你装了nodejs,然后修改下文件server.js里面端口,运行起来node server.js,就可以了,终端会打印出相关信息,把想要跨域域名+端口加在后面就可以绕过跨域问题了。

              万事大吉,let's测试!

              3 Apifox

              按理来说测试Api要使用大名鼎鼎的Postman,但是官网下载了v9的版本打开之后又让我升级v10,打开v10又告诉我你要的功能v10不支持,你得退回v9,想了想这么麻烦,不如我找个替代的吧,一搜还真有不少,Apifox宣称自己Apifox = Postman + Swagger + Mock + JMeter,“API 文档、API 调试、API Mock、API 自动化测试”,还是个国产的,那决定就是你了!

              blabla一通操作进入快捷请求里,选择请求类型为“GET”,地址填上CORS—anywhere反向代理的地址加上Docker Remote API的地址,点击发送。

              结果并没有如第一步中使用curl那样获得运行中容器的信息,那是出了什么问题呢?看看相应信息——响应体中的内容是:

                Missing required request header. Must specify one of: origin,x-requested-with

                缺了个请求头?那填上试试。在Apifox请求里面加上请求头

                  {'x-requested-with':'XMLHttpRequest'}

                  来表示这是个ajax的请求,再一次点击发送,这次成功了。

                  那接下来就是测试容器的控制了,将请求类型设定为“POST”,API按照Docker官方文档中写的/containers/{id}/start/containers/{id}/stop/containers/{id}/restart对应容器的启动、停止和重启命令,一一测试验证查看容器状态,发现一切就如预期。

                  一同操作猛如虎,终于完成了往WebUI上编写的全部前置条件……

                  4 HTML+JS

                  在HTML上写上几个td单元格来展示状态——哦因为我为了偷懒不想算css所以全丢进表格里面了,给上各自的id方便我们的DOM操作,按钮一通加,不要吝啬。

                  js呢就有一些复杂了。

                  首先使在页面加载完成后进行一次状态查询,就是要把容器状态从Docker Remote API那里获取到,然后判断相应的容器是否存在,是否是在运行的状态,最后是修改相关的单元格里的内容,简单点就是一个span,因为它是行内元素,不然还要考虑换行啊float啊,就不优雅。

                  那咱们上代码:

                    document.addEventListener("DOMContentLoaded", function () {


                        $.ajax({
                              methods"get",
                              url"{CORS-anywher反向代理的地址}" + "{Docker Remote API的地址}/containers/json",
                              successfunction (data{
                                for (key in data) {
                                  switch (data[key].Names[0]) {
                                    case "{server镜像的名称}":
                                      if (data[key].State == "running") {
                                        server_status_content.innerHTML =
                                          "<span style='color: green;'>已连接</span>";
                                      }
                                      break;


                                    case "{client镜像的名称}":
                                      if (data[key].State == "running") {
                                        client_status_content.innerHTML =
                                          "<span style='color: green;'>已连接</span>";
                                      }
                                      break;
                                  }
                                }
                              },
                            });
                    })

                    我觉得可行,咱们打开这个页面看一看——怎么没有状态显示??哦我没有设定初始值……加上加上:

                      var server_status_content = document.getElementById("server_status");
                      server_status_content.innerHTML = "<span style='color: red;'>未连接</span>";
                      var client_status_content = document.getElementById("client_status");
                      client_status_content.innerHTML = "<span style='color: red;'>未连接</span>";

                      接着绑定按钮,咱这次用原生JS来:、

                        var start_server_btn = document.getElementById("start_server");


                        start_server_btn.addEventListener("click"() => {
                            var xhr = new XMLHttpRequest();
                            xhr.open(
                              "post",
                              "{CORS-anywher反向代理的地址}" + "{Docker Remote API的地址}/containers/{server容器名称}/start"
                            );
                            xhr.setRequestHeader("X-Requested-With""XMLHttpRequest");
                            xhr.send();
                            xhr.onload = function ({
                              console.log(xhr);
                            };
                        });

                        但是这又会出现一个问题,就是点击完这个按钮,容器状态的改变不会引发前面状态展示的更新,还需要手动刷新一次页面才行,这可不行,我们要重新查询一次容器状态,最好还是有一个延时,免得服务器反应不及23333

                        这样一来就要对之前的代码进行一个复用,一但代码需要复用,咱就是一个考虑封装成一个函数,就命名为refresh_status好了,然后立刻进行一个调用:

                          function refresh_status({
                            var server_status_content = document.getElementById("server_status");
                            server_status_content.innerHTML = "<span style='color: red;'>未连接</span>";
                            var client_status_content = document.getElementById("client_status");
                            client_status_content.innerHTML = "<span style='color: red;'>未连接</span>";
                            jQuery.ajax({
                              methods"get",
                              url"{CORS-anywher反向代理的地址}" + "{Docker Remote API的地址}/containers/json",
                              successfunction (data{
                                for (key in data) {
                                  switch (data[key].Names[0]) {
                                    case "{server镜像的名称}":
                                      if (data[key].State == "running") {
                                        server_status_content.innerHTML =
                          "<span style='color: green;'>已连接</span>";
                                      }
                                      break;


                                    case "{client镜像的名称}":
                                      if (data[key].State == "running") {
                                        client_status_content.innerHTML =
                                          "<span style='color: green;'>已连接</span>";
                                      }
                                      break;
                                  }
                                }
                              },
                            });
                          }


                          refresh_status()

                          于是按钮绑定的事件也进行一下更新:

                            start_server_btn.addEventListener("click"() => {
                                var xhr = new XMLHttpRequest();
                                xhr.open(
                                  "post",
                                  "{CORS-anywher反向代理的地址}" + "{Docker Remote API的地址}/containers/{server容器名称}/start"
                                );
                                xhr.setRequestHeader("X-Requested-With""XMLHttpRequest");
                                xhr.send();
                                xhr.onload = function ({
                                  var delay = setTimeout(refresh_status(), 100);
                                  clearTimeout(delay);
                                };
                            });


                            5 PM2进程管理

                            最近经历了一波网络整顿,可能路由器的超时时间设置的特别短,一会儿就连接中断了,而这些Node.js的脚本又是跟随终端进程的,也就意味着连接中断,node脚本也就被结束运行了,还得重新登录服务器,重新输入命令运行脚本,脚本比较少的时候还没什么不变,好几个主机好几个脚本就很让人苦恼了,所以不得不求助PM2进行进程管理。

                            在终端输入

                              $ npm install pm2@latest -g

                              安装完成后再输入

                                $ pm2 start {app.js}

                                使用pm2守护这个脚本,然后再设置开机自启动:

                                  $ pm2 save
                                  $ pm2 startup

                                  妈妈再也不用担心进程中断啦。

                                  文章转载自雨天做梦,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                  评论