Skip to main content

How Podman can extract a container's external IP address

You can get external IP addresses for both rootfull and rootless containers with Podman.
Image
Containers from above

Podman is a useful tool for deploying and managing containers. In part one of this article series, I covered how to deploy Podman containers and defined the environment I'll use in the rest of the series. In part two, I demonstrated several ways to list running containers and format their output. Read the previous parts first to understand the environment and necessary toolkit.

This article shows how to use Podman to extract information about the container's external Internet Protocol (IP) addresses.

Podman networking concepts

Before getting the container's IPs, it's important to understand some Podman networking concepts. Podman runs both rootfull and rootless containers, which is a great advantage. However, there are slight differences in how Podman manages rootfull and rootless containers and pods. For a more detailed understanding, I strongly recommend you check the official getting started documentation and tutorials and two other fantastic articles on Podman networking and Podman IP address leasing written by Brent Baude on Enable Sysadmin.

To be succinct and simple, when running rootless containers, the container itself does not have an IP address. Without root privileges, network association is not allowed. Rootless containers make use of the slirp4netns network mode. In contrast, rootfull containers use the Container Network Interface (CNI) plugins and specifically the bridge plugin. This allows them to communicate to each other and the external world using their own IP addresses and a bridged and routed network. They can also use network address translation (NAT).

This article explores both scenarios, starting with the rootfull containers.

Explore a rootfull containers network

First of all, using the commands described in the previous article, list the running rootfull containers and pods:

$ sudo podman ps -a

CONTAINER ID  IMAGE                               COMMAND               CREATED         STATUS             PORTS                 NAMES
0158d9f81a96  k8s.gcr.io/pause:3.5                                      50 minutes ago  Up 50 minutes ago  0.0.0.0:8080->80/tcp  a28ca9ac0a93-infra
0326028545fa  docker.io/library/mysql:latest      mysqld                50 minutes ago  Up 50 minutes ago  0.0.0.0:8080->80/tcp  mysql
62cb7ce5c260  docker.io/library/wordpress:latest  apache2-foregroun...  50 minutes ago  Up 50 minutes ago  0.0.0.0:8080->80/tcp  wordpress
2f988bdf14b5  docker.io/library/httpd:latest      httpd-foreground      14 seconds ago  Up 14 seconds ago  0.0.0.0:8081->80/tcp  httpd

$ sudo podman pod ps --ctr-names

POD ID        NAME        STATUS      CREATED         INFRA ID      NAMES
a28ca9ac0a93  blog        Running     50 minutes ago  0158d9f81a96  a28ca9ac0a93-infra,mysql,wordpress

When Podman is installed, a default network is created for it. List the existing Podman networks:

$ sudo podman network ls

NETWORK ID    NAME        VERSION     PLUGINS
2f259bab93aa  podman      0.4.0       bridge,portmap,firewall,tuning

$ sudo podman network inspect podman

[
    {
        "cniVersion": "0.4.0",
        "name": "podman",
        "plugins": [
            {
                "bridge": "cni-podman0",
                "hairpinMode": true,
                "ipMasq": true,
                "ipam": {
                    "ranges": [
                        [
                            {
                                "gateway": "10.88.0.1",
                                "subnet": "10.88.0.0/16"
                            }
                        ]
                    ],
                    "routes": [
                        {
                            "dst": "0.0.0.0/0"
                        }
                    ],
                    "type": "host-local"
                },
                "isGateway": true,
                "type": "bridge"
            },
            {
                "capabilities": {
                    "portMappings": true
                },
                "type": "portmap"
            },
            {
                "type": "firewall"
            },
            {
                "type": "tuning"
            }
        ]
    }
]

Podman's default rootfull network uses the bridge plugin. It is called cni-podman0 and is given a gateway and a subnet. This is the bridge the rootfull containers use to get their external IPs. A veth interface is created for these containers and pod inside a network namespace that is using that bridge, as seen below:

$ sudo nmcli connection show

NAME                UUID                                  TYPE      DEVICE      
Wired connection 1  dbf31db6-f0e9-3b5d-9967-e0ee6b40ba13  ethernet  enp1s0      
cni-podman0         aa419d98-2ca1-4689-a830-7d163a66fbcd  bridge    cni-podman0

# ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 52:54:00:85:c2:f8 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.30/24 brd 192.168.1.255 scope global noprefixroute enp1s0
       valid_lft forever preferred_lft forever
    inet6 fe80::e31f:3806:c76d:89cd/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
3: cni-podman0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 8e:0f:c6:e9:c2:25 brd ff:ff:ff:ff:ff:ff
    inet 10.88.0.1/16 brd 10.88.255.255 scope global cni-podman0
       valid_lft forever preferred_lft forever
    inet6 fe80::8c0f:c6ff:fee9:c225/64 scope link
       valid_lft forever preferred_lft forever
5: veth4b13cc0d@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master cni-podman0 state UP group default
    link/ether 0e:d1:a3:26:54:4d brd ff:ff:ff:ff:ff:ff link-netns cni-576bb654-3c4e-c8df-5ab4-376b3cc71b6e
    inet6 fe80::ec25:e7ff:feb6:14e7/64 scope link
       valid_lft forever preferred_lft forever
6: veth63be543d@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master cni-podman0 state UP group default
    link/ether 06:4d:d2:1f:1f:fb brd ff:ff:ff:ff:ff:ff link-netns cni-52ee91f6-897d-fa1a-b203-bdbf6dc734f6
    inet6 fe80::44d:d2ff:fe1f:1ffb/64 scope link
       valid_lft forever preferred_lft forever

Check the bridge configuration for this interface:

$ sudo bridge link list

5: veth4b13cc0d@enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master cni-podman0 state forwarding priority 32 cost 2
6: veth63be543d@enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master cni-podman0 state forwarding priority 32 cost 2

$ sudo bridge vlan show

port              vlan-id  
cni-podman0       1 PVID Egress Untagged
veth4b13cc0d      1 PVID Egress Untagged
veth63be543d      1 PVID Egress Untagged

Now, from inside the network namespaces these interfaces are in, get the IP addresses set for the containers and pods:

$ sudo ip netns list

cni-52ee91f6-897d-fa1a-b203-bdbf6dc734f6 (id: 1)
cni-576bb654-3c4e-c8df-5ab4-376b3cc71b6e (id: 0)

$ sudo ip netns exec cni-52ee91f6-897d-fa1a-b203-bdbf6dc734f6 ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether be:4c:f7:f0:47:17 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.88.0.4/16 brd 10.88.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::bc4c:f7ff:fef0:4717/64 scope link
       valid_lft forever preferred_lft forever

$ sudo ip netns exec cni-576bb654-3c4e-c8df-5ab4-376b3cc71b6e ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 8a:1e:21:dd:fa:91 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.88.0.3/16 brd 10.88.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::881e:21ff:fedd:fa91/64 scope link
       valid_lft forever preferred_lft forever

But which containers or pods are using each IP? Inspect the containers and pods directly to get this information.

Get external IPs for rootfull containers

The commands to get detailed information about some container or pod are podman inspect and podman pod inspect. Check the podman inspect parameters:

$ podman inspect --help

Display the configuration of object denoted by ID

Description:
  Displays the low-level information on an object identified by name or ID.
  For more inspection options, see:

      podman container inspect
      podman image inspect
      podman network inspect
      podman pod inspect
      podman volume inspect

Usage:
  podman inspect [options] {CONTAINER|IMAGE|POD|NETWORK|VOLUME} [...]

Examples:
  podman inspect fedora
  podman inspect --type image fedora
  podman inspect CtrID ImgID
  podman inspect --format "imageId: {{.Id}} size: {{.Size}}" fedora

Options:
  -f, --format string   Format the output to a Go template or json (default "json")
  -l, --latest          Act on the latest container podman is aware of
                        Not supported with the "--remote" flag
  -s, --size            Display total file size
  -t, --type string     Specify inspect-object type ("image", "container" or "all") (default "all")

Get the external IPs and exposed ports for all containers by inspecting them using the following command with some filters to get only these two outputs:

$ sudo podman inspect httpd -f '{{ .NetworkSettings.IPAddress }} {{ .NetworkSettings.Ports }}'

10.88.0.4 map[80/tcp:[{ 8081}]]

$ sudo podman inspect a28ca9ac0a93-infra -f '{{ .NetworkSettings.IPAddress }} {{ .NetworkSettings.Ports }}'

10.88.0.3 map[80/tcp:[{ 8080}]]

$ sudo podman inspect mysql -f '{{ .NetworkSettings.IPAddress }} {{ .NetworkSettings.Ports }}'

10.88.0.3 map[80/tcp:[{ 8080}]]

$ sudo podman inspect wordpress -f '{{ .NetworkSettings.IPAddress }} {{ .NetworkSettings.Ports }}'

10.88.0.3 map[80/tcp:[{ 8080}]]

Since the httpd container is a standalone container, it has its own IP and exposed port. But the a28ca9ac0a93-infra, MySQL, and wordpress containers are part of the same pod, the blog pod. Therefore, they share the same IP address and exposed port, allowing communication from one container to another inside the same pod by using the pod name, the localhost address, or the shared external IP address.

Validate this by making some communication tests between the wordpress and mysql containers, and then from the wordpress container to the standalone httpd container:

$ sudo podman exec -it wordpress /bin/bash

root@blog:/var/www/html# cat /etc/hosts

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
10.88.0.3	blog a28ca9ac0a93-infra
10.88.0.1 host.containers.internal

root@blog:/var/www/html# curl -v telnet://10.88.0.4:80

*   Trying 10.88.0.4:80...
* Connected to 10.88.0.4 (10.88.0.4) port 80 (#0)
^C

root@blog:/var/www/html# curl -v telnet://10.88.0.3:3306 --output -

*   Trying 10.88.0.3:3306...
* Connected to 10.88.0.3 (10.88.0.3) port 3306 (#0)
J
* RCVD IAC 2
* RCVD IAC 223
^C

root@blog:/var/www/html# curl -v telnet://blog:3306 --output -

*   Trying 10.88.0.3:3306...
* Connected to blog (10.88.0.3) port 3306 (#0)
J
* RCVD IAC 2
* RCVD IAC 223
^C

$ sudo podman exec -it httpd /bin/bash

root@2f988bdf14b5:/usr/local/apache2# cat /etc/hosts

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
10.88.0.4	2f988bdf14b5 httpd
10.88.0.1 host.containers.internal

Use the external IP addresses to test communication between the local host where the containers reside and the containers:

$ sudo curl -v http://10.88.0.4

*   Trying 10.88.0.4:80...
* Connected to 10.88.0.4 (10.88.0.4) port 80 (#0)
> GET / HTTP/1.1
> Host: 10.88.0.4
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Fri, 10 Dec 2021 21:26:34 GMT
< Server: Apache/2.4.51 (Unix)
< Last-Modified: Tue, 07 Dec 2021 21:29:58 GMT
< ETag: "74-5d295133b0ae6"
< Accept-Ranges: bytes
< Content-Length: 116
< Content-Type: text/html
<
<html>
  <header>
    <title>Enable SysAdmin</title>
  </header>
  <body>
    <p>Hello World!</p>
  </body>
</html>
* Connection #0 to host 10.88.0.4 left intact

$ sudo curl -v http://10.88.0.3 | grep "Enable SysAdmin" | head -1

*   Trying 10.88.0.3:80...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to 10.88.0.3 (10.88.0.3) port 80 (#0)
> GET / HTTP/1.1
> Host: 10.88.0.3
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Fri, 10 Dec 2021 21:29:00 GMT
< Server: Apache/2.4.51 (Debian)
< X-Powered-By: PHP/7.4.26
< Link: <http://192.168.1.30:8080/index.php/wp-json/>; rel="https://api.w.org/"
< Link: <http://192.168.1.30:8080/index.php/wp-json/wp/v2/pages/10>; rel="alternate"; type="application/json"
< Link: <http://192.168.1.30:8080/>; rel=shortlink
< Vary: Accept-Encoding
< Transfer-Encoding: chunked
< Content-Type: text/html; charset=UTF-8
<
{ [8211 bytes data]
100 21955    0 21955    0     0   462k      0 --:--:-- --:--:-- --:--:--  476k
* Connection #0 to host 10.88.0.3 left intact
	<title>Enable SysAdmin &#8211; Just another WordPress site</title>

$ sudo curl -v telnet://10.88.0.3:3306 --output -

*   Trying 10.88.0.3:3306...
* Connected to 10.88.0.3 (10.88.0.3) port 3306 (#0)
J
* RCVD IAC 2
* RCVD IAC 223
^C

You could also create your own network configurations that the containers could use to get custom IP addresses from the local network.

Recall that the information demonstrated so far is related to rootfull containers. What about rootless containers?

[ Take a look at 10 considerations for Kubernetes deployments. ]

Explore a rootless containers network

You can use the same images to deploy rootless containers in the same fashion as the rootfull containers, as shown below:

$ podman ps

CONTAINER ID  IMAGE                               COMMAND               CREATED         STATUS             PORTS                 NAMES
c23ef920ebad  docker.io/library/httpd:latest      httpd-foreground      54 seconds ago  Up 53 seconds ago  0.0.0.0:8081->80/tcp  httpd
a07edaeae104  k8s.gcr.io/pause:3.5                                      13 seconds ago  Up 11 seconds ago  0.0.0.0:8080->80/tcp  ab3cb4c917bd-infra
f8fbe57cb219  docker.io/library/mysql:latest      mysqld                13 seconds ago  Up 11 seconds ago  0.0.0.0:8080->80/tcp  mysql
43d9972f5220  docker.io/library/wordpress:latest  apache2-foregroun...  10 seconds ago  Up 8 seconds ago   0.0.0.0:8080->80/tcp  wordpress

But a quick look at the network configuration shows a difference:

$ ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 52:54:00:85:c2:f8 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.30/24 brd 192.168.1.255 scope global noprefixroute enp1s0
       valid_lft forever preferred_lft forever
    inet6 fe80::e31f:3806:c76d:89cd/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

Rootless containers use a different Podman networking plugin, slirp4netns. Therefore, in order to check the rootless networking information, you must find the containers' network namespace path. Use podman unshare and nsenter to enter these network namespaces, and then check the tap0 interface or virtual device there:

$ ps -ef | grep slirp

localus+    2333       1  0 13:50 pts/0    00:00:00 /usr/bin/slirp4netns --disable-host-loopback --mtu=65520 --enable-sandbox --enable-seccomp -c -e 3 -r 4 --netns-type=path /run/user/1000/netns/cni-a1f72a2b-46f5-175d-67cc-e914c6e361be tap0
localus+    2583       1  0 13:50 pts/0    00:00:00 /usr/bin/slirp4netns --disable-host-loopback --mtu=65520 --enable-sandbox --enable-seccomp -c -r 3 --netns-type=path /run/user/1000/netns/rootless-cni-ns tap0
localus+    4176    1500  0 14:10 pts/0    00:00:00 grep --color=auto slirp

$ podman unshare nsenter --net=/run/user/1000/netns/cni-a1f72a2b-46f5-175d-67cc-e914c6e361be

# ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: tap0: <BROADCAST,UP,LOWER_UP> mtu 65520 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether f6:1a:cf:db:97:6b brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0
       valid_lft forever preferred_lft forever
    inet6 fe80::f41a:cfff:fedb:976b/64 scope link
       valid_lft forever preferred_lft forever

$ podman unshare nsenter --net=/run/user/1000/netns/rootless-cni-ns

# ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: tap0: <BROADCAST,UP,LOWER_UP> mtu 65520 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether 5a:22:95:1e:af:21 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0
       valid_lft forever preferred_lft forever
    inet6 fe80::5822:95ff:fe1e:af21/64 scope link
       valid_lft forever preferred_lft forever
3: cni-podman0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether f2:66:6c:73:26:8b brd ff:ff:ff:ff:ff:ff
    inet 10.88.0.1/16 brd 10.88.255.255 scope global cni-podman0
       valid_lft forever preferred_lft forever
    inet6 fe80::f066:6cff:fe73:268b/64 scope link
       valid_lft forever preferred_lft forever
4: veth5fc4083f@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master cni-podman0 state UP group default
    link/ether aa:81:ec:b7:61:78 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::a881:ecff:feb7:6178/64 scope link
       valid_lft forever preferred_lft forever

The rootless containers have no external IP addresses attached to them. So how can you communicate with them?

Get external IPs for rootless containers

To get a rootless container's IP, use the same process as before but with a different output:

$ podman inspect httpd -f '{{ .NetworkSettings.IPAddress }} {{ .NetworkSettings.Ports }}'

 map[80/tcp:[{ 8081}]]

$ podman inspect mysql -f '{{ .NetworkSettings.IPAddress }} {{ .NetworkSettings.Ports }}'

10.88.0.2 map[80/tcp:[{ 8080}]]

$ podman inspect wordpress -f '{{ .NetworkSettings.IPAddress }} {{ .NetworkSettings.Ports }}'

10.88.0.2 map[80/tcp:[{ 8080}]]

As seen above, the httpd standalone container doesn't get an external IP address, but the mysql and wordpress containers get an external IP address because they're inside the same pod, which uses the bridge network by default.

How can you communicate with these rootless containers without an external IP address? Connect by using either the localhost address or the IP address from the host node interface through the containers' exposed ports. Check it out:

$ podman exec -it wordpress /bin/bash

root@blog:/var/www/html# curl -v telnet://10.88.0.2:3306 --output -

*   Trying 10.88.0.2:3306...
* Connected to 10.88.0.2 (10.88.0.2) port 3306 (#0)
J
* RCVD IAC 2
* RCVD IAC 223
^C

root@blog:/var/www/html# curl -v telnet://127.0.0.1:3306 --output -

*   Trying 127.0.0.1:3306...
* Connected to 127.0.0.1 (127.0.0.1) port 3306 (#0)
J
* RCVD IAC 2
* RCVD IAC 223
^C

root@blog:/var/www/html# curl -v telnet://192.168.1.30:8081

*   Trying 192.168.1.30:8081...
* Connected to 192.168.1.30 (192.168.1.30) port 8081 (#0)
^C

It is important to remember whether containers are rootfull or rootless when getting IP address information from the containers so that you know the best way to interact with them and how to configure the networking accordingly.

Optimize communications between applications

Podman offers a network abstraction that makes life much easier for container users, but it is important to understand its concepts to optimize communication between applications. This article shows how to get this information and extract IP addresses and other network information from containers. The next article in this series show how to update container images with Podman.

Topics:   Podman   Containers   Networking  
Author’s photo

Alexon Oliveira

Alexon has been working as a Senior Technical Account Manager at Red Hat since 2018, working in the Customer Success organization focusing on Infrastructure and Management, Integration and Automation, Cloud Computing, and Storage Solutions. More about me

Try Red Hat Enterprise Linux

Download it at no charge from the Red Hat Developer program.