Rust-Although 9 days have passed, the result is no result (port mapping problem of Docker container)

?

This article is included in the Rust practical column. The relevant code in this column comes from the note-taking system I developed. It starts on September 14, 2023. Relevant technology stacks currently include: Rust, Javascript. Follow me, I will share with you relevant practical technologies through the development of this project.

Foreword

Last week, I wrote an article about the pitfalls of Rust-backend service debugging. Now it seems that the pitfalls are nothing at all. This time the pit is the real pit. At the moment of writing this article, I have only identified the simplest code to reproduce this problem (refer to Directory Verification 4: Creating the simplest Rust http service based on the Debian:11 image). But still no solution to this problem has been found.

Now I will sort out this problem and my verification process so that friends passing by can understand it. I believe there must be a master who knows the reason and solution. I hope you can leave a message and give me some advice.

Problem description

I created a Restful service api (hereinafter referred to as api) based on the Rocket framework (0.5-rc) using the Rust language and deployed it in a docker container for debugging.

Container related description:

  • Container name: notes-api
  • Network mode: bridge
  • Port mapping: 8003:8000

When the container is successfully started, the problem and related situations are described:

  • The api in the container cannot be accessed from the host machine, error code 56, command line: curl http://localhost:8003/api/notes
  • Able to ping the container from the host, command line: ping 172.22.0.2
  • Log in to the container and be able to run the command line to successfully access the API. Command line: sudo docker exec -it notes-api curl http://localhost:8000/api/notes
  • If you change the network mode to host, you can access the API in the container normally from the host. Command line: curl http://localhost:8000/api/notes

Doubts about oneself

This question caused me to question my Docker experience and related memory. Is it possible that the Docker container cannot be accessed from the host machine when the network mode is bridge? Soon, this problem was denied, because I had configured routing for multiple APIs on the Nginx server earlier. These APIs were deployed on one server through Docker and accessed through different ports deployed in these containers. api.
Since it was earlier, my next judgment was whether it was because the Docker version installed on my current server was relatively new, and whether the new version of Docker Engine had some settings regarding port mapping, causing the deployment in Docker What if the API in the container cannot be accessed normally?

The road to verification

1. Python application

To be honest, my experience with using Docker containers to deploy APIs mainly comes from Python. This is my first time deploying a Rust-built API into a Docker container. Therefore, I decided to create a simple api in Python and deploy it into a Docker container to see if it could be accessed normally from the host machine.
docker-compose.yml

version: '3'

services:
  web:
    build:
      context: ./
      dockerfile: Dockerfile
    container_name: python-api
    network_mode: bridge
    ports:
      - 8004:5000

Dockerfile

from python:latest

workdir /app
copy ./main.py ./requirements.txt /app/

run pip install -r requirements.txt
cmd ["python3", "main.py"]

Result: The API in the container can be successfully accessed from the host.

So, is it a problem with the mirror itself?
I compared the Linux systems used by the Python image and the Rust image. The Python image uses “Debian GNU/Linux 11”, and the Rust image uses “Debian GNU/Linux 12”.

2. Create a Rust image from the Debian:11 image

Therefore, I decided to make an image of the Rust api container based on the Debian:11 image.
docker-compose.yml

version: '3'
services:
  web:
    build:
      context: ./
      dockerfile: Dockerfile
    container_name: notes-api1
    network_mode: bridge
    ports:
      - 8004:8000

Dockerfile

from debian:11
run apt update
run apt install -y curl gcc

run curl -s --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
env PATH=/root/.cargo/bin:$PATH
run rustup default nightly

workdir /app

copy ./Cargo.toml ./config.toml ./Cargo.lock /app/
copy ./src /app/src

cmd ["cargo", "run"]

Result: The api in the Docker container cannot be accessed from the host machine.
Could it be that although the Python image is based on “Debian GNU/Linux 11”, there are some settings regarding port mapping when making the image?

3. Create a Python image from the Debian:11 image

Therefore, in order to eliminate the guesswork about “settings”, I decided to make an image of the Python api container based on the Debian:11 image.
If it can be accessed normally, it means there is no guess about “settings”.
docker-compose.yml

version: '3'

services:
  web:
    build:
      context: ./
      dockerfile: Dockerfile
    container_name: my-python2
    network_mode: bridge
    ports:
      - 8009:5000

Dockerfile

from debian:11
run apt update
run apt install -y curl gcc

run apt install python3 pip -y

workdir /app
copy ./main.py ./requirements.txt /app/

env PATH=/usr/local/bin:$PATH

run pip install -r requirements.txt

cmd ["python3", "main.py"]

Result: The API in the container can be successfully accessed from the host.
Note that in the Python image, the relevant “settings” conjecture about port mapping does not exist.

Therefore, my attention returned to the Rust project. My api was developed based on Rocket 0.5-rc. Is it because of this framework?

4. Create the simplest Rust http service based on Debian:11 image

Therefore, I asked AI to help me write a Rust http service without any dependencies, and then deployed this service to a Debian:11-based image.
docker-compose.yml

version: '3'
services:
  web:
    build:
      context: ./
      dockerfile: Dockerfile
    container_name: rust-simple-http
    network_mode: bridge
    ports:
      - 8012:8080

Dockerfile

from debian:11
run apt update
run apt install -y curl gcc

# Install rust
run curl -s --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
env PATH=/root/.cargo/bin:$PATH

workdir /app

copy ./Cargo.toml ./entry.sh /app/
copy ./src/main.rs /app/src/main.rs

entrypoint ["./entry.sh"]

main.rs

use std::io::{<!-- -->Read, Write};
use std::net::{<!-- -->TcpListener, TcpStream};

fn handle_client(mut stream: TcpStream) {<!-- -->
    let mut buffer = [0; 1024];
    stream.read( & amp;mut buffer).unwrap();

    let response = "HTTP/1.1 200 OK\r\\
\r\\
Hello, World!";
    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

fn main() {<!-- -->
    let listener = TcpListener::bind("127.0.0.1:8080").unwrap();

    for stream in listener.incoming() {<!-- -->
        match stream {<!-- -->
            Ok(stream) => {<!-- -->
                std::thread::spawn(|| {<!-- -->
                    handle_client(stream);
                });
            }
            Err(e) => {<!-- -->
                eprintln!("Failed to establish a connection: {}", e);
            }
        }
    }
}

entry.sh

#!/bin/bash

echo "cargo build"
cargo build --release
echo "cp bin"
cp target/release/app ./
echo "run app"
./app

Result: The API in the Docker container cannot be accessed from the host machine.
Therefore, it seems to have nothing to do with the Rocket 0.5-rc framework.

The current situation can be summarized as:

  1. The API created by Rust is deployed into the container. The network mode of the container is bridge. The API running in the container cannot be accessed from the host through the port of the container.
  2. The API created by Python is deployed into the container. The network mode of the container is bridge. The API running in the container can be accessed from the host through the port of the container.

Does this special phenomenon only exist between Rust and Python? If we find another application and deploy it to a Docker container with the network mode being bridge, can we access the application running in the container from the host through the container port? If the answer is “yes”, then we can more or less conclude:

The api created by Rust is deployed into a Docker container with a bridge network mode. The api running in the container cannot be accessed from the host through the port of the container.

5. Install a simple http service from apt based on Debian:11 image

docker-compose.yml

version: '3'
services:
  web:
    build:
      context: ./
      dockerfile: Dockerfile
    container_name: simple-http-server
    network_mode: bridge
    ports:
      - 8013:8080

Dockerfile

from debian:11
run apt update
run apt install -y curl gcc

run apt install libhttp-server-simple-perl -y

workdir /app
copy entry.sh /app/
entrypoint ["./entry.sh"]

entry.sh

#!/bin/bash
perl -MHTTP::Server::Simple -e 'my $server = HTTP::Server::Simple->new(); $server->run()'

Result: The API in the container can be successfully accessed from the host.

Conclusion

It seems that the only conclusion we can draw is this:
The api created by Rust is deployed into a Docker container with a bridge network mode. The api running in the container cannot be accessed from the host through the port of the container.

The questions have been sorted out, so I’ll put them here first.

How to carry out the subsequent work

Temporarily set the container’s network mode to host for debugging. If there are many services configured later, you may need to create a cli to manage the configuration of each service port (this is my strength, haha).

If you have any questions, please leave a message to communicate. Follow me and I will bring you more sharing about the actual development of Rust in the Rust practical column.

?