Monday, July 25, 2022

Docker 101 - Network



Network Drivers



List all networks (the list below is the default network created by Docker since we have not created any networks yet). Reference

Ex: 
$ docker network ls

Result:
NETWORK ID     NAME      DRIVER    SCOPE
09a5f34a0bb8   bridge    bridge    local
  f419db6a9e21   host host      local
  0175d39fdfe6   none null      local

*bridge:
The default network driver.
If you don’t specify a driver, this is the type of network you are creating.
Bridge networks are usually used when your applications run in standalone containers that need to communicate.

*host:
For standalone containers, remove network isolation between the container and the Docker host, and use the host’s networking directly.

*none(null):
 For this container, disable all networking.
 Usually used in conjunction with a custom network driver.


Bridge network



*In terms of networking: 
         A bridge network is a Link Layer device which forwards traffic between network segments.

*In terms of Docker:
         A bridge network uses a software bridge which allows containers connected to the same bridge network to communicate. It provides isolation from containers which are not connected to that bridge network.

Reference: document1, document2


Default bridge



When you start Docker, a default bridge network (also called bridge) is created automatically, and newly-started containers connect to it unless otherwise specified

First of all, let's check our network interface in our docker host.

Ex: 
$ ifconfig

Result:
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
inet6 fe80::42:62ff:fe8b:caa7  prefixlen 64  scopeid 0x20<link>
  ether 02:42:62:8b:ca:a7  txqueuelen 0  (Ethernet)
  RX packets 0  bytes 0 (0.0 B)
  RX errors 0  dropped 0  overruns 0  frame 0
  TX packets 9  bytes 1041 (1.0 KB)
  TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

'docker0' is the interface Docker created for us for default bridge network.

Ex: 
$ docker network inspect bridge

Result:
[
{
"Name": "bridge",
"Scope": "local",
"Driver": "bridge",
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Containers": {},
"Labels": {},
...
}
]

Therefore, the gateway of the default bridge will be docker0 in Docker host.

Then, let's create two container without specifying network.

Ex: 
$ docker container run -d -it --name alpine_default_1 alpine sh
$ docker container run -d -it --name alpine_default_2 alpine sh

Result:
bd334a1aa24f97f61d048a7f459b8e7a93ac479805993f2b510fbae47c88abb7
  93122bbae5a31bf10041b2fd473244d34621ee563eabf496d33ba7346e10b8e5

Check the detail of each container separately.

Ex: 
$ docker container inspect bd33

Result:
"NetworkSettings": {
...
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "09a5f34a0bb8118d5f064a6496c939a1ece5a817...",
"EndpointID": "755aa48ce8fdc5d6c750cd71f541d40069b2b06...",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02",
"DriverOpts": null
}
}
}

Ex: 
$ docker container inspect 9312

Result:
"NetworkSettings": {
...
    "Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "09a5f34a0bb8118d5f064a6496c939a1ece5a817...",
"EndpointID": "9b9d9e1efa86c27ee1dd20f70bed3776f7f15ce...",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:03",
"DriverOpts": null
}
    }
}

Ex: 
$ docker network inspect bridge

Result:
[
{
"Name": "bridge",
"Scope": "local",
"Driver": "bridge",
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Containers": {
"93122bbae5a31bf10041b2fd473244d34621ee563eabf496d...": {
"Name": "alpine_default_2",
"EndpointID": "9b9d9e1efa86c27ee1dd20f70bed3776f7f15ce7...",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
},
"bd334a1aa24f97f61d048a7f459b8e7a93ac479805993f2b...": {
"Name": "alpine_default_1",
"EndpointID": "755aa48ce8fdc5d6c750cd71f541d40069b2b069...",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Labels": {}
}
]

Once it is all set, let's verify the connection between them.
Let's try to ping containers by IP address first.

Ex: 
$ docker container exec -it alpine_default_1 sh
/ # ping -c 1 172.17.0.3

Result:
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.056 ms

--- 172.17.0.3 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.056/0.056/0.056 ms

Let's try to ping containers by the container name.

Ex: 
/ # ping -c 1 alpine_default_2

Result:
ping: bad address 'alpine_default_2'

Opps, since we are using the default bridge, we cannot use container name to find its IP address automatically.


User-defined Bridge



Docker allows us to create user-defined bridge, and actually the document indicates that we should use user-defined bridge instead of default bridge for those reasons.

Lets' create a user-defined bridge to dive deeper.

Ex: 
$ docker network create my-bridge

Result:
14988e726d808d3af667636c0e8051de92bca0d80a64f0f90aa65cbd555575fb

Ex: 
$ docker network ls

Result:
NETWORK ID     NAME        DRIVER    SCOPE
09a5f34a0bb8   bridge      bridge    local
f419db6a9e21   host        host      local
14988e726d80   my-bridge   bridge    local
0175d39fdfe6   none        null      local

Ex: 
$ docker network inspect my-bridge

Result:
[
{
"Name": "my-bridge",
"Scope": "local",
"Driver": "bridge",
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Containers": {},
"Labels": {}
}
]

Ex: 
$ ifconfig

Result:
br-14988e726d80: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
inet 172.18.0.1  netmask 255.255.0.0  broadcast 172.18.255.255
ether 02:42:67:8c:86:48  txqueuelen 0  (Ethernet)
RX packets 0  bytes 0 (0.0 B)
RX errors 0  dropped 0  overruns 0  frame 0
TX packets 0  bytes 0 (0.0 B)
TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
inet6 fe80::42:62ff:fe8b:caa7  prefixlen 64  scopeid 0x20<link>
ether 02:42:62:8b:ca:a7  txqueuelen 0  (Ethernet)
RX packets 6  bytes 308 (308.0 B)
RX errors 0  dropped 0  overruns 0  frame 0
TX packets 15  bytes 1644 (1.6 KB)
TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

The gateway of the new created user-defined bridge will be br-14988e726d80 network in Docker host.

Then create containers and specify the network with this user-defined bridge.

Ex: 
$ docker container run -d -it --name alpine_user_defined_3 \
--network my-bridge alpine sh
$ docker container run -d -it --name alpine_user_defined_4 \
--network my-bridge alpine sh

Result:
8b43bd1b39467d8cacaf6a20e5f7c1cacbee288c55a3bfb73dba9cd89a9a6a6c
2e91d9793bc1fb4d740c5ca4df9a8c22f8fe4616c3c63ad388943bb248e464c0

Ex: 
$ docker container inspect 8b43

Result:
"NetworkSettings": {
"Networks": {
"my-bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": [
"8b43bd1b3946"
],
"NetworkID": "14988e726d808d3af667636c0e8051de92bca0d80...",
"EndpointID": "fa8c9a1bbf57750bfed8c23049fdce56f7da7575...",
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:12:00:02",
"DriverOpts": null
}
}
}

Ex: 
$ docker container inspect 2e91

Result:
"NetworkSettings": {
"Networks": {
"my-bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": [
"2e91d9793bc1"
],
"NetworkID": "14988e726d808d3af667636c0e8051de92bca0d80...",
"EndpointID": "f91c5cda192c8a9c48f2a46630ab542758dc0fe1...",
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:12:00:03",
"DriverOpts": null
}
}
}

Since we are using user-undefined bridge, we can use container name now.

Ex: 
$ docker container exec -it alpine_user_defined_3 sh
/ # ping -c 1 alpine_user_defined_4

Result:
PING alpine_user_defined_4 (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.135 ms

--- alpine_user_defined_4 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.135/0.135/0.135 ms


Port Expose



From the Dockerfile, the EXPOSE command is just as a type of documentation.
It informs the developers that the container created by this image will need to listens on the specified network ports at runtime.

Therefore, when running your container, you need to use -p to publish ports.
The exposed port is accessible on the host and the ports are available to any client that can reach the host. Reference


Ex: 
$ docker container run -d --name web1 \
--network my-bridge -p 8080:80 nginx

Result:
f8594b7f3db8824386923a38664e92b70363dfb8c82c45d46cb1e370aff6f3dc

Then we can access it with that port in our Docker host.

Ex: 
$ wget localhost:8080

Result:
Resolving localhost (localhost)... 127.0.0.1
Connecting to localhost (localhost)|127.0.0.1|:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 615 [text/html]

index.html            100%[===============>]     615  --.-KB/s    in 0s

Let's check the iptables to see what rules Docker added.

Ex: 
$ sudo iptables -t nat -nvxL

Result:
Chain DOCKER (2 references)
target   prot opt source     destination
RETURN   all   --   0.0.0.0/0  0.0.0.0/0
RETURN   all   --   0.0.0.0/0  0.0.0.0/0
DNAT     tcp   --   0.0.0.0/0  0.0.0.0/0  tcp dpt:8080 to:172.18.0.2:80


No comments:

Post a Comment