Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docker Support #11

Closed
Zibbp opened this issue Jan 31, 2022 · 16 comments · Fixed by #129
Closed

Docker Support #11

Zibbp opened this issue Jan 31, 2022 · 16 comments · Fixed by #129
Labels

Comments

@Zibbp
Copy link

Zibbp commented Jan 31, 2022

Is Docker support planned? A docker container for the tracker and backend/frontend would be amazing.

https://blog.logrocket.com/packaging-a-rust-web-service-using-docker/

@PseudoResonance
Copy link

I built my own Docker image just to keep it more organized when running, but I ran into the issue that Torrust can't get the correct client IPs while inside Docker. There's a relevant issue here discussing the problem and some potential solutions, like giving the container host network access.
moby/moby#15086

@mickvandijke
Copy link
Member

I'm planning on adding Docker images, after I add support for reverse proxy's 😄

@Zorlin
Copy link

Zorlin commented Feb 19, 2022

I'll happily package this for Kubernetes once Docker support is done, as a bonus.

josecelano added a commit to josecelano/torrust-tracker that referenced this issue Jul 6, 2022
@josecelano josecelano linked a pull request Jul 6, 2022 that will close this issue
@josecelano
Copy link
Member

I've created a PR with a Dockerfile only for this service. We could create docker images also for the backend and frontend. I think it would be also helpful to create a docker-compose file in order to build and run all the services with a single command. That it would make much easier to start coding and also it would make it easier for other developers to try things without having to install all the dependencies.

The docker-compose configuration could be located in a new repo. The other option could be generate docker-compose file only for the torrust-index. So when you start working you need to run two commands.

Right now developers are likely to work on all the projects at the same time but I suppose as the project became bigger you probably are not going to work on both the tracker and the index at the same time.

In conclusion, one docker-compose file for the torrust-index could be a good option and we can add it to the torrust-index repo.

@Zorlin
Copy link

Zorlin commented Jul 6, 2022

Hi @josecelano - take a look at https://github.com/zorlin/torrust-docker ...

:)

@josecelano
Copy link
Member

Hi @josecelano - take a look at https://github.com/zorlin/torrust-docker ...

:)

Hi @Zorlin cool!!!

Just a couple of questions/comments:

  1. I think it would be nice to have a new docker layer for tracker and backend with only the dependencies as described here.
  2. It seems your repo was designed to build a production release with the latest version of each repo. I was thinking more about using docker-compose for development. In that case I think it makes more sense to get the code from the context instead of with git clone.
  3. I do not know why to put the binaries inside the docker image for Rust but you mount the node app in the caddy image ($PWD/data/frontend/dist/:/srv). Why do not you build the app in the Dockerfile?
  4. It seems setup would be easier if the default config file is already included in the git repo instead of being generated on the fly by the app the first time is executed. We would get rid of some entry.sh scripts. But I suppose you followed the current installation steps. I think generating the default config file at runtime with default hardcoded values in the code instead of a default config file is an extra indirection level that may not be necessary, but I have not looked at the generation code, maybe there are some values that are calculated at runtime.
  5. If we want to use docker for development, do you think would it make sense to move the Dokerfiles to their own repos? I have not used docker for Rust, since you have to compile after every change I do not know if it makes sense to use docker. Either you only used it as a virtual machine (you compile and start manually) or you can install a "watch" process to re-compile and restart the service every time you change a file. Does it make sense? If it does not make sense, then your approach of having an independent repo for docker config files is the best approach in my opinion.
  6. Even if the tracker and backend have the same dependencies for production I think I would use two different docker images in the docker-compose file. It could be harder to split them later if you start adding dependencies that are only needed in one of the services.

I'm going to follow your instructions to run it on my laptop and I will give you feedback.

@josecelano josecelano linked a pull request Dec 12, 2022 that will close this issue
17 tasks
@Power2All
Copy link
Contributor

@josecelano
If you want some tips about Docker deployment.
I already made one of my own separate project here, you could use that as well for your usage:

https://github.com/Power2All/torrust-axum/blob/master/docker/Dockerfile

Use it however you like though, it's suppose to be used in combination with either a direct host connection, or could be used in combination with Traefik/NGINX for instance.
Configuration is done through the init.sh which you can find there as well, which build the configuration file based on the ENVIRONMENT variables.

It's not perfect, but it works pretty good.

@josecelano
Copy link
Member

Traefik/NGINX

Thank you, @Power2All I've been looking at it. I'm working on this PR.

I have defined some requirements on the PR. Feedback for the requirements is welcome. I have not added an explicit goal for the PR, but mainly I want to:

  • Write the basic docker configuration for development and production that could be used to build your own deployments (wherever you want to deploy it, if you want to use docker).
  • Try to find a very easy way to deploy the application. I'm testing Azure Container Instances because the docker console command has support for it, and I thought most developers are familiar with it.
  • I think docker is the best way to write "live documentation" for your application's system dependencies.

I see you have also added SSL support for the API.

@Power2All
Copy link
Contributor

Thank you, @Power2All I've been looking at it. I'm working on this #123.

No problem, been busy with a new job I just started at, so I've not been around much to work on the code lately anyhow. Good thing the project is still moving :)

I see you have also added SSL support for the API.

Correct, wasn't really that hard though.
Basically, just added the same thing as it was for the other sockets ;)

@josecelano josecelano linked a pull request Dec 16, 2022 that will close this issue
@josecelano
Copy link
Member

I built my own Docker image just to keep it more organized when running, but I ran into the issue that Torrust can't get the correct client IPs while inside Docker. There's a relevant issue here discussing the problem and some potential solutions, like giving the container host network access. moby/moby#15086

hi @PseudoResonance could you elaborate on this? how can I reproduce that problem? I've been running the tracker with docker locally and on an Azure Container Instance with the standard configuration and without using a reverse proxy but I have tested only the UDP tracker and the API.

@Power2All
Copy link
Contributor

I built my own Docker image just to keep it more organized when running, but I ran into the issue that Torrust can't get the correct client IPs while inside Docker. There's a relevant issue here discussing the problem and some potential solutions, like giving the container host network access. moby/moby#15086

hi @PseudoResonance could you elaborate on this? how can I reproduce that problem? I've been running the tracker with docker locally and on an Azure Container Instance with the standard configuration and without using a reverse proxy but I have tested only the UDP tracker and the API.

Probably need to use X-Forwarded-For and UDP doesn't know origin connecting client. This needs to be looked into.

@PseudoResonance
Copy link

I built my own Docker image just to keep it more organized when running, but I ran into the issue that Torrust can't get the correct client IPs while inside Docker. There's a relevant issue here discussing the problem and some potential solutions, like giving the container host network access. moby/moby#15086

hi @PseudoResonance could you elaborate on this? how can I reproduce that problem? I've been running the tracker with docker locally and on an Azure Container Instance with the standard configuration and without using a reverse proxy but I have tested only the UDP tracker and the API.

It's been quite a long time, but I believe if you setup a docker container and only open the port, Docker's networking will end up masking the IP, as Power2All said. The easiest solution being to simply bypass Docker and use host networking, and another solution being to use a reverse proxy to set X-Forwarded-For before the network traffic reaches Docker.

@josecelano
Copy link
Member

josecelano commented Dec 19, 2022

Hi, I've been testing the docker container. I have deployed the tracker to a droplet following this article by @mrdaniel.

I was able to set up the three services:

I've explained all the details here.

For the API and the HTTP tracker, I'm using Nginx as a reverse proxy (with certbot).

I could not set up a reverse proxy for the UDP tracker, but you can access the docker port because, by default, it bypasses the firewall rules.

Regarding IP and Ports, I would like to understand all the available settings. I think you can have all the scenarios described below (is it OK @WarmBeer?):

HTTP API

  • It works using the docker port directly (1212) or a reverse proxy (443), and I think there is no option in the settings affecting the API IP/port configuration.

HTTP Tracker

  • It works using the docker port directly (7070) or a reverse proxy (443).

Settings:

It uses two options:

  • on_reverse_proxy: it's true is the track is behind a reverse proxy.
  • external_ip: it's the public tracker IP.

The peer address IP is calculated in two places:

  1. When the HTTP web framework builds the request torrust_tracker::http::request::Announce
  2. When the HTTP web framework handles the request.

The /announce endpoint (warp filter):

/// GET /announce or /announce/<key>
fn announce(tracker: Arc<tracker::Tracker>) -> impl Filter<Extract = impl warp::Reply, Error = Rejection> + Clone {
    warp::path::path("announce")
        .and(warp::filters::method::get())
        .and(with_announce_request(tracker.config.on_reverse_proxy))
        .and(with_auth_key())
        .and(with_tracker(tracker))
        .and_then(handle_announce)
}

If on_reverse_proxy is true, the peer address in the torrust_tracker::http::request::Announce request will be the HTTP header X-Forwarded-For address. The header is mandatory if on_reverse_proxy is enabled.

The handler:

pub async fn handle_announce(
    announce_request: request::Announce,
    auth_key: Option<auth::Key>,
    tracker: Arc<tracker::Tracker>,
) -> WebResult<impl Reply> {
    // ...
    let peer = peer::Peer::from_http_announce_request(&announce_request, announce_request.peer_addr, tracker.config.get_ext_ip());
    let torrent_stats = tracker
        .update_torrent_with_peer_and_get_stats(&announce_request.info_hash, &peer)
        .await;

The handle_announce function calls the Peer::peer_addr_from_ip_and_port_and_opt_host_ip. This is the second check. If the remote IP is a loopback address, it's replaced with the IP in the external_ip option.

UDP Tracker

The UDP tracker only calculates the peer IP while handling the request. There is no HTTP header.

Notes/Questions:

  • I was not able to set up a reverse proxy for UDP.
  • If possible, I suppose Nginx (or the reverse proxy being used) should change the source IP when it sends the request to the docker port, and it should change the destination IP again before sending the response back to the client. Does anyone know anything about this?
  • If not possible, I suppose you have to expose the docker socket directly. Is that what you mean by use host networking @PseudoResonance?

Extra info

I do not know how the docker network mode "host" relates to this issue and potential problems using the tracker with docker.

To make it clear, there are two problems:

  • The peer_addr in the announce response should be the real client IP.
  • In the UDP tracker, the response has to be sent to the real client IP. I suppose the proxy (if any) should handle that in a non-transparent way.

Example response from the API with the peer for the URL: https://tracker-api.josecelano.site/api/torrent/0b3aea4adc213ce32295be85d3883a63bca25446?token=***

{
  "info_hash": "0b3aea4adc213ce32295be85d3883a63bca25446",
  "seeders": 1,
  "completed": 0,
  "leechers": 0,
  "peers": [
    {
      "peer_id": {
        "id": "2d5452333030302d726a7339376b753078346273",
        "client": "Transmission"
      },
      "peer_addr": "YOUR_PUBLIC_IP:51413",
      "updated": 1671465070905,
      "updated_milliseconds_ago": 1671465070905,
      "uploaded": 0,
      "downloaded": 0,
      "left": 0,
      "event": "Started"
    }
  ]
}

Conclusions

Regarding docker configuration, I suppose you can run the tracker with docker with the limitation of exposing the UDP port directly. But I suppose that is not a docker problem but a proxy limitation.

Any suggestions or comments? I would like to merge the docker support #129 PR if it has no problems.

@Power2All
Copy link
Contributor

@josecelano
HTTP/HTTPS can obtain a client IP using the X-Forwarded-For header.
UDP should also work when it comes to origin IP and port. From what I read you need to make sure it's directly bind to the host machine it runs on, since proxying UDP traffic will not preserve the client IP.

This might be interesting reading:
moby/libnetwork#1994

@josecelano
Copy link
Member

UDP traffic will not preserve the client IP.

Exposing the UDP tracker directly

In my case, it works with docker-compose when I expose the docker port using a wildcard socket (0.0.0.0:6969). I'm not using the "host" network driver. I'm using the "bridge" network driver.

$ docker network ls
NETWORK ID     NAME                                 DRIVER    SCOPE
88c881bc7a28   droplet-docker-context_server_side   bridge    local
$ docker network inspect 88c881bc7a28
[
    {
        "Name": "droplet-docker-context_server_side",
        "Id": "88c881bc7a283b4f092cda1d38b8a5d308c010cd7eb8ae2bdba31572497e76fe",
        "Created": "2022-12-20T09:29:27.419222891Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.21.0.0/16",
                    "Gateway": "172.21.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "7b1b83242f0251c0a1c750e8ca1e11da3375e959abf3d6f5a0022675724130ad": {
                "Name": "droplet-docker-context-mysql-1",
                "EndpointID": "fc59a0ea0e75842f0b01bce64ab4e1342dcb22c50f500887c7976e21b2e849f3",
                "MacAddress": "02:42:ac:15:00:02",
                "IPv4Address": "172.21.0.2/16",
                "IPv6Address": ""
            },
            "e9ef561bd4f00aff2d32ab369f69c7f869e7c6df6d7df14734137e3dae7ecdb0": {
                "Name": "droplet-docker-context-tracker-1",
                "EndpointID": "4266c74316affc996559c2ded6ee4daa138d0be4a732a0de9b4a5a8ca4b04fc9",
                "MacAddress": "02:42:ac:15:00:03",
                "IPv4Address": "172.21.0.3/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "server_side",
            "com.docker.compose.project": "droplet-docker-context",
            "com.docker.compose.version": "2.13.0"
        }
    }
]
$ docker ps
CONTAINER ID   IMAGE                                              COMMAND                  CREATED       STATUS                                 PORTS                                                                                                                             NAMES
e9ef561bd4f0   josecelano/torrust-tracker:docker-reorganized-pr   "/app/torrust-tracker"   6 hours ago   Up 6 hours                             0.0.0.0:1212->1212/tcp, :::1212->1212/tcp, 0.0.0.0:7070->7070/tcp, :::7070->7070/tcp, 0.0.0.0:6969->6969/udp, :::6969->6969/udp   droplet-docker-context-tracker-1
7b1b83242f02   mysql:8.0                                          "docker-entrypoint.s…"   7 hours ago   Up About a minute (health: starting)   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp    

From docker docs:

Note that ports which are not bound to the host (i.e., -p 80:80 instead of -p 127.0.0.1:80:80) will be accessible from the outside. This also applies if you configured UFW to block this specific port, as Docker manages its own iptables rules

I read in this article that you should bind UDP services to a specific IP address because the system does not know where you like to source the packet from.

I did an experiment with the tracker and with another sample application which is a UDP echo server.

Instead of binding to the wildcard socket (0.0.0.0:6969) I used the container IP, for example: 172.21.0.3:6969. In docker-compose you can assign a concrete IP to the container:

services:
  echo-udp:
    build:
      context: .
    ports:
      - 8080:8080/udp
    networks:
      app_net:
        ipv4_address: 172.16.239.10
    command: "172.16.239.10:8080"

networks:
  app_net:
    name: upd_echo_net
    ipam:
      driver: default
      config:
        - subnet: "172.16.239.0/24"

I tested both options, and It works. You can test it:

cd /tmp
git clone git@github.com:josecelano/awesome-torrust-tracker-compose.git
cd awesome-torrust-tracker-compose/droplet-echo-udp-context/
docker compose up

You should see:

$ docker compose up
[+] Running 1/0
 ⠿ Container droplet-echo-udp-context-echo-udp-1  Created                                                                                                0.0s
Attaching to droplet-echo-udp-context-echo-udp-1
droplet-echo-udp-context-echo-udp-1  | Binding on : 172.16.239.10:8080
droplet-echo-udp-context-echo-udp-1  | Listening on: 172.16.239.10:8080

You can open a new terminal and execute:

nc -u 127.0.0.1 8080

If you type something, will see the output on the previous tab:

droplet-echo-udp-context-echo-udp-1  | Echoed 6/6 bytes to 172.16.239.1:35368

It also works with the container IP nc -u 172.16.239.10 8080 defined in the docker-compose.ym.

Exposing the UDP tracker behind an Nginx reverse Proxy

I could not set up the Nginx as a reverse proxy for the UDP tracker. This is not a problem with the docker configuration. I suppose the Nging configuration is wrong. I'm using this configuration:

stream {
        log_format basic '$remote_addr [$time_local] '
                         '$protocol $status $bytes_sent $bytes_received '
                         '$session_time';
        access_log /var/log/nginx/access_stream.log basic;
        error_log /var/log/nginx/error_stream.log;
        upstream udpserver {
                server 172.16.239.10:8080;
        }
        server {
                listen 8181 udp;
                proxy_pass udpserver;
                proxy_bind $remote_addr:$remote_port transparent;
                proxy_responses 0;
        }
}

The Nginx and the "udp echo" container are listening:

$ sudo netstat -tulpn | grep :8181
udp        0      0 0.0.0.0:8181            0.0.0.0:*                           9871/nginx: master  
deployer@josecelano-torrust-tracker:~$ sudo netstat -tulpn | grep :8080
udp        0      0 0.0.0.0:8080            0.0.0.0:*                           9607/docker-proxy   
udp6       0      0 :::8080                 :::*                                9612/docker-proxy

I can connect to the container remotely (using the server's public IP) but not to the Nginx.

I also tried to bind the container socket to the container IP, but it did not work.

I tried with and without the option:

proxy_bind $remote_addr:$remote_port transparent;

As described here that

_"enables the upstream server to observe the full source IP address, so it can construct response datagrams that are sent directly to the remote client.

The upstream server generates response (“egress”) packets with the correct IP destination, but using its local IP address as the source address. The source address needs to be rewritten to the IP address and port of the NGINX Plus load balancer that the client originally connected to."_

It seems that with that option, the tracker container or the echo UDP container should be able to send the response directly to my public IP (on my local machine). But I do not receive any response.

$ sudo netstat -tulpn | grep :8181
udp        0      0 0.0.0.0:8181            0.0.0.0:*                           9871/nginx: master  
deployer@josecelano-torrust-tracker:~$ sudo netstat -tulpn | grep :8080
udp        0      0 0.0.0.0:8080            0.0.0.0:*                           9607/docker-proxy   
udp6       0      0 :::8080                 :::*    

The docker processes:

deployer   17427  0.0  3.4 1190292 34024 pts/1   Sl+  17:43   0:00 docker run -it --publish 8080:8080/udp echo-udp 172.17.0.2:8080
root       17447  0.0  0.3 1148320 3188 ?        Sl   17:43   0:00 /usr/bin/docker-proxy -proto udp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.17.0.2 -container-port 8080
root       17453  0.0  0.3 1148320 3184 ?        Sl   17:43   0:00 /usr/bin/docker-proxy -proto udp -host-ip :: -host-port 8080 -container-ip 172.17.0.2 -container-port 8080
root       17470  0.0  0.8 712204  8680 ?        Sl   17:43   0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 1a022d50f0c944e98d7db6cebbc593cff2ac18e8bd456ea413296223c7b5e8b4 -addr
deployer   17491  0.0  0.0   3120     4 pts/0    Ssl+ 17:43   0:00 /app/echo-udp 172.17.0.2:8080

Anyway, I think this problem is out of the scope of this issue. The UDP tracker works fine with docker.

@Power2All
Copy link
Contributor

@josecelano Nice, thanks for also jumping into it :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Archived in project
6 participants