0

I'm currently testing an Ansible role using Molecule. Basically, Molecule launches a container that is Ansible compliant and runs the role on it.

In order to test the container, Molecule also embed unit tests using Testinfra. The python unit tests are run from within the container so you can check the compliance of the role.

As I'm working on an Nginx based role, one of the unit tests is simply issuing a curl http://localhost:80

I do get the below error message in response:

curl: (7) Failed to connect to localhost port 80: Connection refused

When I:

  • launch a Vagrant machine
  • apply the role with Ansible
  • connect via vagrant ssh
  • issue a curl http://localhost command

nginx answers correctly.

Therefore, I believe that:

  • the role is working properly and Nginx is installed correctly
  • Docker has a different way to set-up the network. In a way, localhost and 127.0.0.1 are not the same anymore.

My questions are the following:

  1. Am I correct?
  2. Can this difference be overcome so the curl would work?
E. Jaep
  • 2,095
  • 1
  • 30
  • 56
  • `127.0.0.1` is still `localhost` in Docker - at least, with default settings. However, your application could occasionally be set up to bind to the container's `veth` interface rather than to all interfaces. Try to find the address your application is bound to using `ss` or `netstat`. – Danila Kiver Jul 20 '18 at 13:11
  • @DanilaKiver: How comes `curl http://127.0.0.1` fails in a docker container but not on a virtualbox machine? If `127.0.0.1` is still 'what is is supposed to be', this should work just fine. – E. Jaep Jul 20 '18 at 13:45
  • `127.0.0.1` is just an IP address, assigned to the specific interface (usually `lo`). If application is bound to the specific address/interface other than `127.0.0.1`/`lo` (e.g. container's `veth` interface), it will not respond to requests to `127.0.0.1` - that's how sockets work. This is why I propose to see which address your application is actually bound to. Answering the "why the application binds to different addresses in different environments?" question is the next step, if you'll find that my hypothesis is true. – Danila Kiver Jul 20 '18 at 13:55
  • apparently, no port is listening (`ss -lt`): `State Recv-Q Send-Q Local Address:Port Peer Address:Port ` and no process is running (`ps -ef | grep -i nginx`): ``` root 1454 1453 0 08:04 ? 00:00:00 /bin/sh -c ps -ef | grep nginx root 1456 1454 0 08:04 ? 00:00:00 grep nginx ``` When the results against a Vagrant machine are totally different. – E. Jaep Jul 23 '18 at 08:22

2 Answers2

1

Docker containers start in their own network namespace by default. This namespace includes a separate loopback interface (127.0.0.1) that is distinct from the same interface on the host and any other containers. If you want to access an application from another container or via a published port on the host, you need to listen on all interfaces (0.0.0.0) rather than the loopback interface.

One other issue I often see is at some layer in the connection (the host, or inside of a container), the "localhost" name is mapped to the IPv6 value of ::1 in the /etc/host file, and somewhere in that connection only the IPv4 value is valid (either where the port was published, the application is listening, or IPv6 isn't enabled on the host or docker engine). Therefore, make sure to try connecting to the IPv4 address directly, 127.0.0.1, to eliminate any potential IPv6 issues.

Regarding the curl command and how to correct it, I cannot answer that without more details on how you are running the curl (is it in a separate container), how you are running your application, and how the two are joined on the network (did you create a new network in docker for your application and unit tests to run). The typical solution is to create a new network in docker, run both containers on that network, and connect via docker's included DNS to the container or service name of the destination, e.g. curl http://my_app/.


Edit: based on the comments, if your application and curl command are both running inside the same container, then curl http://127.0.0.1/ should work. There's no change I'm aware of needed with to curl to make it work inside of a container vs on a VM. The error you are seeing is likely from the application not starting and listening on the port as expected, possibly a race condition where the curl command is run too soon, or the base assumptions of how the tool works is incorrect. Start by changing the unit test to verify the application is up and running and listening on the port with commands like ps -ef and ss -lt.

BMitch
  • 231,797
  • 42
  • 475
  • 450
  • 1
    thanks for the answer. The curl is actually running from within the same container. – E. Jaep Jul 20 '18 at 15:03
  • @E.Jaep If you are running `curl` inside your container, are you also running the webserver in that container? What command did you launch the container with? – BMitch Jul 20 '18 at 15:07
  • 1
    Yes. I installed nginx with Ansible. – E. Jaep Jul 20 '18 at 15:18
  • @E.Jaep Installed nginx "where" with Ansible? Inside the same container, in a different container, or are you trying to connect from container to host? If nginx is inside the same container, how are you starting both curl and nginx in the same container? We need to see your entrypoint or command. – BMitch Jul 20 '18 at 15:26
  • 1
    Basically, Molecule generates a Dockerfile that is used to create the container. Then docker execs are used to run Ansible playbooks on the same container. Everything is happening within 1 container. There is no notion of multiple containers having to talk to each other. – E. Jaep Jul 20 '18 at 15:32
  • Surprisingly, the testinfra test stating `assert host.socket("tcp://:::80").is_listening` does not fail. Please give me some time to test `ps -ef` and `ss -lt` and I'll come back to you. Thank you already for your time. – E. Jaep Jul 20 '18 at 17:15
  • apparently, no port is listening (`ss -lt`): `State Recv-Q Send-Q Local Address:Port Peer Address:Port ` and no process is running (`ps -ef | grep -i nginx`): ``` root 1454 1453 0 08:04 ? 00:00:00 /bin/sh -c ps -ef | grep nginx root 1456 1454 0 08:04 ? 00:00:00 grep nginx ``` When the results against a Vagrant machine are totally different. – E. Jaep Jul 23 '18 at 08:23
  • Actually you are right. /etc/hosts file localhost is mapped to IPV6 `::1 localhost` – Manjunath Reddy Nov 08 '18 at 13:58
0

it actually have nothing to do with the differences between Docker and Vagrant (i.e. containers vs VMs).

The testInfra code is actually run from outside the container / VM, hence the fact the subprocess.call(['curl', 'http://localhost']) is failing.

In order to run a command from the container / VM, I should use:

host.check_output('curl http://localhost')
E. Jaep
  • 2,095
  • 1
  • 30
  • 56