Erlang:为什么inet:gethostbyname提供两个地址,而inet:getaddr只提供一个

cgyqldqp  于 2022-12-08  发布在  Erlang
关注(0)|答案(1)|浏览(173)
The problem

I used inet:gethostbyname(Hoststr) in a docker environment with a couple containers for over a year without problems. Due to non recoverable read errors on the SSD which -- according to the provider -- is perfectly fine I was forced to reinstall from scratch with a CentOS image.
After restore I get a crash which boils down to

3> inet:gethostbyname("www").
{ok,{hostent,"www",
             ["www"],
             inet,4,
             [{10,0,1,205},{10,0,1,180}]}}

obviously because I get 2 IPs.
getaddr works fine:

4> inet:getaddr("www", inet).
{ok,{10,0,1,205}}
Question

I can replace gethostbyname with getaddr , no problem, but I would like to know why I get 2 IPs in the first place and how this misbehavior could creep in.
PHP does just fine:

id=$(docker ps -a  | grep "vx_www.1\." | grep -v "xited" | awk '{print $1}') && docker exec -it $id ash
php > echo gethostbyname('www');
10.0.1.205
Is it a docker problem?

The problem may lie on the docker side, as both addresses ping.
It gets even more interesting (from the host):

/ # ip a | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b"
127.0.0.1
10.0.1.205
10.0.1.255
172.18.0.13
172.18.255.255
10.0.8.33
10.0.8.255
10.0.0.162
10.0.0.255
10.0.9.19
10.0.9.255

This should show addresses from within the docker system, if I understand correctly, and the latter address is not in the list. But it is somewhere:

# docker inspect sa3oizstflg3  | grep "10.0.1"
                    "Addr": "10.0.1.180/24"
What is a VirtualIP?

Actually I get this address with the ID of www as VirtualIP together with a bunch of others

"VirtualIPs": [
            {
                "NetworkID": "y3rf5yes97yjs1rfzpzf4aou8",
                "Addr": "10.0.0.148/24"
            },
            {
                "NetworkID": "swagio8bzavl2bf5u5bivmt13",
                "Addr": "10.0.1.180/24"
            },
            {
                "NetworkID": "tnmpad21shpvsps6fps5m4own",
                "Addr": "10.0.8.5/24"
            },

                {
                    "NetworkID": "mz9aogx7jxit8x2bflgpyh5lh",
                    "Addr": "10.0.9.2/24"
                }
The same container listens to 2 different IPs

Taking a different container with PHP on board, I get the second address given by inet:gethostbyname("www") for the same container, so both seem to be correct and usable:

# id=$(docker ps -a  | grep "vx_wsm.1\." | grep -v "xited" | awk '{print $1}') && docker exec -it $id ash
    / # php -a
    Interactive shell
    
    php > echo gethostbyname('www');
    10.0.1.180

Now I am confused. Anybody knows what is happening here?
inet:gethostbyname seems to be not wrong but more correct, then.

Erlang question

As an addendum: I am not that proficient in Erlang. In my code it reads:

get_ip_web(Web) ->                                          % e.g. www = > 100.0.1.226
    [case X of $, -> $.; _ -> X end || X <- lists:flatten(io_lib:format("~p",element(6, element(2, inet:gethostbyname(Web))))), X=/=${, X=/=$}].

How to rewrite this fine piece of code to pick one of the two addresses, but working also with only one result?
Well, this is mostly an academic question as I didn't understand this one-liner at all initially -- my comment was incomprehensible. This is no longer the case, but I still struggle with constructs easy to handle in other languages, especially if there is no routine for a long time.
For your information: I replaced the one-liner above with this much simpler one-liner:
inet:ntoa(element(2,inet:getaddr(Web, inet)))

px9o7tmv

px9o7tmv1#

Erlang:

From the code, inet:getaddr/2 just gets the first ip from inet:getaddrs/2 , which in turn gets them from gethostbyname .
You should use the hostent record instead of element (only if you go with functions that return a hostent , I'd rather use the getaddr like you did in the end, it's actually the same code):

-module(test).
-include_lib("kernel/include/inet.hrl").
-export([get_ip/0]).

get_ip() ->
    {ok, #hostent{h_addr_list = [IP|_]}} = inet:gethostbyname("www.stackoverflow.com"),
    inet:ntoa(IP).

Docker:

If you run ip -a in the host, you'll get only the ips for the host, not the ones used by containers. Usually the host has an IP in each of the bridges that make the different docker networks, so it's in the same range as the containers.
Regarding the several ips, I don't have experience with docker swarm (which it seems that you're using), only with kubernetes.
My guess is that you have a service exposed in several docker networks that shares the name ( www ) with the container that implements it. Thus, in the network of the www container you have DNS both resolve www to the container IP and the service virtualIP address. Maybe you can find the virtual ips in iptables/nftables?

相关问题