This article is published under a SIGNATURE 4.0 International (CC BY 4.0) license. Signature 4.0 International (CC BY 4.0)

Author: Su Yang

Creation time: April 04, 2019 statistical word count: 6010 words reading time: 12 minutes to read this article links: soulteary.com/2019/04/04/…


Better container use of Aria2

While we were cleaning up HomeLab, we encapsulated Aria2 as a container image.

This article will show you how to use Aria2 quickly with Traefik and how to adjust incompatible Traefik applications to be compatible with it.

The mirror is introduced

Before I start explaining how to do this, I need to talk a little bit about how mirror images are made.

For long running applications/services, it is important that the code is transparent and open. I have uploaded the code to GitHub: code repository.

The stack/tools used include the following:

  • Aria2 WebUI – An Aria2 Web management interface ziahamza/webui-aria2
  • Aria2-aria2 Download tools NDthuan/ariA2-alpine
  • Nodehttp proxy-node. js nodejitsu/ Node-http-proxy
  • Traefik – Containous/Traefik is an excellent load balancing/service discovery tool
  • Docker/Compose – Docker/Compose is a simple container composing tool
  • Docker – Easy to use container tool Docker

Transformation process

It is popular to use a container to provide HTTP + ARIA2 services at the same time on the Internet, but this kind of fat container actually does not conform to the principle of “single process single container”. More generally speaking, using containers in this way is no different from using VM virtual machine.

So we need to run these two parts separately, using the docker-comemage. yml definition, perhaps the simplest example is as follows:

version: '3'

services:

  web:
    container_name: web
    image: ${WEBUI_IMAGE}
    expose:
      - 8888

  aria2:
    container_name: aria2
    image: ${ARIA2_IMAGE}
    volumes:
      - ./downloads:/downloads
    expose:
      - 6800
Copy the code

However, if you use the Aria2 WebUI directly in the container, you will find that the Web management interface writes out the RPC port to 6800, because the project design only considers co-deployment, and Aria2 is executed in the default configuration.

The reason for this is that the project itself is a set of SPA applications written in Angular and does not have server-side interface capabilities. However, when the project is running in production, the author provides a simple Node Server that supports some degree of interface customization.

We used the container to separate the Web UI from Aria2, which is equivalent to executing the two applications on two different servers, so we need to make some changes here.

WebUI supports separate deployment from Aria2

There are two references that affect WebUI use of the Aria2 RPC interface:

/app/src/js/services/configuration.js
/app/src/js/services/rpc/rpc.js
Copy the code

First deal with the contents of configuration.js:

host: location.protocol.startsWith("http")? location.hostname :"localhost",
    path: "/jsonrpc",
    port: 6800,
    encrypt: false.Copy the code

Go to the code above in the program and change port 6800 to 80.

Next, process the contents of rpc.js:

if (["http"."https"].indexOf(uri.protocol()) ! = -1 && uri.host() ! ="localhost") {
                configurations.push(
                    {
                        host: uri.host(),
                        path: "/jsonrpc",
                        port: 6800,
                        encrypt: false
                    },
Copy the code

Also find this code and change port 6800 to 80.

After the code is modified, you will find that all the requests on the WebUI are located to port 80 under the current domain name. As mentioned above, the interface is a front-end SPA application and lacks interface processing capability.

Therefore, next we need to deal with the Server side. I used ES6 to rewrite the author’s Node Server, mainly supporting HTTP/WS protocol to forward to the corresponding port of ariA2 container.

const { createServer } = require("http");
const url = require("url");
const { join, extname } = require("path");
const { exists, statSync, readFile } = require("fs");

const port = parseInt(process.argv[2], 10) || 8888;
const baseDir = join(process.cwd(), "docs");

const httpProxy = require('http-proxy');

const { WEB_PROXY, WS_PROXY } = process.env;
const webProxy = httpProxy.createProxyServer({ target: WEB_PROXY });
const wsProxy = httpProxy.createServer({ target: WS_PROXY, ws: true });

createServer(function (request, response) {
    const uri = url.parse(request.url).pathname;
    let filename = join(baseDir, uri);

    let contentType = "text/html";
    switch (extname(filename)) {
        case ".js":
            contentType = "text/javascript";
            break;
        case ".css":
            contentType = "text/css";
            break;
        case ".ico":
            contentType = "image/x-icon";
            break;
        case ".svg":
            contentType = "image/svg+xml";
            break;
    }

    if (filename.endsWith("jsonrpc")) {
        webProxy.web(request, response);
        return;
    }

    exists(filename, function (exists) {
        if(! exists) { response.writeHead(404, {"Content-Type": "text/plain" });
            response.write("404 Not Found\n");
            response.end();
            return;
        }

        if (statSync(filename).isDirectory()) filename += "/index.html";

        readFile(filename, "binary".function (err, file) {
            if (err) {
                response.writeHead(500, { "Content-Type": "text/plain" });
                response.write(`${err}\n`);
                response.end();
                return;
            }
            response.writeHead(200, { "Content-Type": contentType });
            response.write(file, "binary");
            response.end();
        });
    });
}).on('upgrade'.function (req, socket, head) {
    wsProxy.ws(req, socket, head);
}).listen(port);

console.log(`WebUI Aria2 Server is running on http://localhost:${port}`);
Copy the code

Static resources are served directly by node.js HTTP modules when the user accesses the admin interface. When a user queries the download status and adds a new download task, node.js will call the Aria2 interface for processing, and the result will be fed back to the user using HTTP or WS.

Build the mirror

To facilitate deployment and maintenance, we need to record the above changes in a Dockerfile.

FROM node:11.12-alpine
LABEL AUTHOR soulteary

ADD webui-aria2/package.json /app/package.json
ADD webui-aria2/package-lock.json /app/package-lock.json

WORKDIR /app

RUN npm install -g yarn && yarn

ADD webui-aria2/ /app

ADD ./patches/configuration.js /app/src/js/services/configuration.js
ADD ./patches/rpc.js /app/src/js/services/rpc/rpc.js

RUN yarn build

RUN yarn add http-proxy
ADD ./patches/node-server.js /app/node-server.js

CMD [ "node"."./node-server.js" ]
Copy the code

Run the Docker build-t soulteary/ traefik-aria2-with-webu. after a while you will get the packaged container image.

The next step is to use the container Choreography tool.

Orchestration applications

Aria2 image, can use ndthuan/ ariA2-alpine, no need to repackage, of course, if you need some new features, you can also refer to the image provided by the Dockerfile, to rebuild.

To create a docker-comemage.yml, use the following sample code:

version: '3'

services:

  web:
    container_name: web
    image: ${WEBUI_IMAGE}
    expose:
      - 8888
    networks:
      - traefik
    environment:
      - WEB_PROXY=http://aria2:6800
      - WS_PROXY=ws://aria2:6800
    labels:
      - "traefik.enable=true"
      - "traefik.port=8888"
      - "traefik.frontend.rule=Host:${BIND_HOSTS}"
      - "traefik.frontend.entryPoints=http,https"

  aria2:
    container_name: aria2
    image: ${ARIA2_IMAGE}
    volumes:
      - ./downloads:/downloads
    expose:
      - 6800
    networks:
      - traefik
    labels:
      - "traefik.enable=false"

networks:
  traefik:
    external: true
Copy the code

Then create a.env file and fill it with:

WEBUI_IMAGE=soulteary/traefik-aria2-with-webui
ARIA2_IMAGE=ndthuan/aria2-alpine

BIND_HOSTS=download.lab.com,download.lab.io
Copy the code

With docker-compose up, you’ll see something like the following:

Creating web   ... done
Creating aria2 ... done
Attaching to aria2, web
aria2    | 2019-04-04T04:28:41Z 48a00033e530 confd[7]: INFO Backend set to env
aria2    | 2019-04-04T04:28:41Z 48a00033e530 confd[7]: INFO Starting confd
aria2    | 2019-04-04T04:28:41Z 48a00033e530 confd[7]: INFO Backend nodes setto aria2 | 2019-04-04T04:28:41Z 48a00033e530 confd[7]: INFO Target config /etc/aria2.conf out of sync aria2 | 2019-04-04T04:28:41Z 48a00033e530 confd[7]: INFO Target config /etc/aria2.conf has been updated aria2 | aria2 | 04/04 04:28:41 [WARN] Neither --rpc-secret nor a combination of --rpc-user and --rpc-passwd is set. This is insecure. It is extremely recommended to specify --rpc-secret  with the adequate secrecy or now deprecated --rpc-user and --rpc-passwd. aria2 | aria2 | 04/04 04:28:41 [NOTICE] IPv4 RPC: listening on TCP port 6800 aria2 | aria2 | 04/04 04:28:41 [NOTICE] IPv6 RPC: listening on TCP port 6800 web | WebUI Aria2 Server is running on http://localhost:8888Copy the code

Open the domain you defined in.env in your browser and, unsurprisingly, you’ll see your downloaded app.

The last

Changing habits is not easy, especially if you are surrounded by other seemingly “standard” solutions.

But once you get used to Traefik + Docker, you’ll find that your services are much more efficient than Nginx plus vhost.

Recently busy dizzy, close to a small holiday, complete this article dragged ten days, written in haste, unavoidable mistakes, welcome comments correction.


I now have a small toss group, which gathered some like to toss small partners.

In the case of no advertisement, we will talk about software, HomeLab and some programming problems together, and also share some technical salon information in the group from time to time.

Like to toss small partners welcome to scan code to add friends. (Please specify source and purpose, otherwise it will not be approved)

All this stuff about getting into groups