Understand how to connect that container to the network:
Create container with network exposure appropriate for the application your running.
Use network software in one container from another
Understand how containers interact with the host and host’s network.
A network interface has an address and represents a location.The important thing to know about IP addresses is that they are unique in their network and contain information about their location on their network.
There are two types of network interface:Ethernet interface and a loop back interface.Ethernet interface is used to connect to other interfaces and processes.Loop back interface is able to use network protocols to communicate with other programs on the same computer.
Interfaces are single points in larger networks.Networks are defined in the way that interfaces are linked together and that linkage determines an interface’s IP address.
When we consider two specific networks.The first network is the one that your computer is connected to.The second is a virtual network that docker creates to connect all the running containers to the network that the computer is connected to .That second network is called a bridge.A bridge is an interface that connects multiple networks so that they can function as a single network.Bridges work by selectively forwarding traffic between the connected networks based on another type of network address.
Docker container networking:
Docker is concerned with two types of networking:Single-host virtual networks and multi-host networks.Local virtual networks are used to provide container isolation.Multi-host virtual networks provide an overlay where any container on a participating host can have its own routable IP address from any other container in the network.
The local Docker network topology:
Docker uses features of the underlying operating system to build a specific and customizable virtual network topology.
The virtual network is local to the machine where docker is installed and is made up of routes between participating containers and wider networks where the host is attached.We can change the behavior of that network structure itself by using command-line options for starting the docker daemon and each container.
Containers have their own private loopback interface and a separate Ethernet interface linked to another virtual interface in the host’s namespace.These two linked interfaces form a link between the host’s network stack and the stack created for each container.
Just like typical home networks,each container is assigned a unique private IP address that’s not directly reachable from the external network.connections are routed through the Docker bridge interface called Dockero.
You can think of the dockero interface like your home router.Each of the virtual interfaces created for containers is linked to dockero and together they form a network.This bridge interface is attached to the network where the host is attached.
Using the docker command-line tool,you can customize the ip addresses used ,the host interface that dockero is connected to ,and the way containers communicate with each other.The connections between interfaces describe how exposed or isolated any specific network.
Container is from rest of network.Docker uses kernel namespaces to create those private virtual interfaces,but the namespace itself doesn’t provide the network isolation.Network exposure or isolation is provided,there are four archetypes for network containers.
Four network container archetypes:
All Docker containers follow one of four archetypes.These archetypes define how a container interacts with the other local containers and the host’s network.Each serves a different purpose, and you can think of each as having a different level of isolation.When you use Docker to create a container,it’s important to carefully consider what you want to accomplish and use the possible container with out compromising that goal.
The four are archetypes are these:
Closed containers,Bridged containers,Joined containers,Open containers.
Closed containers:
Processing running in such a container will have access only to a loopback interface.If they need to communicate only with themselves or each other.The closed archetype has no connection to the docker bridge interface.
All Docker containers,including closed containers have access to private loopback interface.You may have experience working with loopback interfaces already.It’s common for people with moderate experience to have used localhost or 127.0.0.1 as an address in a URL.In these cases you were telling a program to bind to or contact a service bound to your computer’s loopback network interface.
You can tell docker to create a closed container by specifying none with the --net flag as an argument to the docker run command.
docker run --rm \
--net none \
alpine:latest \
ip addr
(create a closed container) (List the interfaces)
Running this example,you can see that the only network interface available is the loopback interface,bound to the address 127.0.0.1.
Note:
Closed containers should be used when the need for network isolation is the highest or whenever a program doesn’t require network access.For example,running a terminal text editor shouldn’t require network access.Running a program to generate a random password should be run inside a container without network access to prevent the theft of that number.
Bridged containers:
Bridged containers relax network isolation and in doing so make it simpler to get started.This archetype is the most customizable and should be hardened as a best practice.
Bridged containers have a private loopback interface and another private interface that’s connected to the rest of the host through a network bridge.
All interfaces connected to dockero are part of the same virtual subnet.This means they can talk to each other and communicate with the larger networks through the dockero interface.
Examples:
docker run --rm \ (Join the bridge network)
--net bridge \
alpine:latest \
ip addr (list the container interfaces).
The output will include details like the IP address and subnet mask of each interface,the maximum packet size (MTU),and various interface metrics.
Now that you have verified that your container has another interface with an IP address,try to access the network again.This time omit the –net flag to see that bridge is the default .
Docker network container type:
docker run --rm \
alpine:latest \ Note omission of the --net option.
ping -w 2 8.8.8.8 Run ping command against Google.
By this we can know that,if we needs to access the internet or some other computer on a private network,you can use a bridged container.
Custom name resolution:
Domain name system (DNS) is a protocol for mapping host names to IP addresses.This mapping enables clients to decouple from a dependency on a specific host IP and instead depend on whatever host is referred to by known name.One of the most basic ways to change outbound communications is by creating names for IP addresses.
It is typical for containers on the bridge network and other computers on your network to have IP addresses that aren’t publicy routable.This means that unless you are running your own DNS server,You cannot refer to them by name.Docker provides different options for customizing the DNS configuration for a new container.
First,the docker run command has a --hostname flag that you can use to set the host name of a new container.This flag adds an entry to the DNS override system inside the container.The entry maps the provided host name to the container’s bridge IP address:
docker run --rm \
--hostname barker \ (set the container host name)
alpine:latest \
nslokup barker (resolve the host name to an IP address).
(This example creates a new container with the host name barker and runs a program to look up the IP address for the same name.Running this example will generate output that looks something like the following :
-----------------------------------------------------
server: 10.0.2.3
Address 1 :10.0.2.3
Name : Barker
Address 1: 172.17.0.22
---------------------------------------------------------
The IP address on the last line is bridge IP address for the ne wcontainer.The IP address provided on the line labeled server is the address of the server that provided the mapping.
Setting the hostname of a container is useful when programs running inside a container need to look up their own IP address or must identify .Because other containers don’t know this hostname,its uses are limited.But if you use an external DNS server,you can share those hostnames.
The second option for customizing the DNS configuration of a container is the ability to specify one or more DNS server to use.To demonstrate ,the following example creates a new container and sets the DNS server for that container to Google’s public DNS service.
docker run --rm \
--dns 8.8.8.8 \ (Set primary DNS server)
alpine:latest \
nslookup docker.com (Resolve IP address of docker.com)
Using a specific DNS server can provide consistency if your running docker on a laptop and often move between internet service providers .It’s critical tool for people building services and networks.There are a few important notes on setting your own DNS server.
The value must be an IP address.If you think about it,the reason is obvious;the container needs a DNS server to perform the lookup on a name.
The --dns=[] flag can be set multiple times to set multiple DNS servers (in case one or more are unreachable).
The --dns=[] flag can be set when you start up the Docker daemon that runs in the background .When you do so,those DNS servers will be set on every container by default.But if you stop the daemon with conatiners running and change the default when you restart the daemon.the running containers will still have the old DNS settings.You will need to restart those containers for the change to take effect.
The third DNS-related option,--dns-search =[],allows you to specify a DNS search do –main,which is like a default host name suffix.With one set,any host names that don’t have a known top-level domain (like .com or .net)will be searched for with the specified suffix appended.
docker run –rm \
--dns-search docker.com \ (set search domain)
busybox:latest \
nslookup registry .hub (Look up shortcut for registry.hub.docker.com)
This command will resolve to the IP address of registry.hub.docker.com because the DNS search domain provided will complete the host name .
Examples:
This feature is most often used for trivialities like shortcut names for internal corporate networks.For example,your company might maintain an internal documentation wiki that you can simply reference at http://wiki/. But this can be much more powerful.
Suppose you maintain a single DNS server for your development and test environments.Rather than building environment-aware software(with hard-coded environment –specific names like myservice.dev.mycompany.com),you might consider using DNS search domains and using environment-unware names (like service)
docker run --rm \
--dns-search dev.mycompany \ (Note dev prefix)
busybox:latest \
nslookup myservice (Resolve to myservice.dev.mycompany)
docker run --rm \
--dns-search test.mycompany \ (Note test prefix)
busybox :latest
nslookup myservice (Resolve to myservice.test.mycompany).
Using this pattern,the only change is the context in which the program is running.Like providing custom DNS servers.You can provide several custom search domains for the same container.Simply set the flag as many times as you have search domains
Example:
Docker run --rm \
--add-host test:10.10.10.255 \ (Add host entry)
alpine:latest \
nslookup test (Resolves to 10.10.10.255)
Like --dns and --dns-search.this option can be specified multiple times.But unlike those other option,this flag cannot be set as a default at daemon startup.
docker run --rm \
--hostname mycontainer \
--add-host docker.com:127.0.0.1 \ (set host name )
--add-host test :10.10.10.2 \ (create another host entry)
alpine:latest \
cat /etc/hosts (view all entries).
Output that looks something like the following:
127.0.0.1 localhost
: :1 localhost ip6-localhost ip6-loopback
fe00: :0 ip6-localnet
ff00: :0 ip6-mcastprefix
ff02: :1 ip6-allnodes
ff02: :2 ip6-allrouters
10.10.10.2 test
127.0.0.1 docker.com
----------------------------------------------------------------------------------------------
DNS is a powerful system for changing behavior.The name-to-IP address map provides a simple interface that people and program can use to decouple themselves from specific network addresses.If DNS is your best tool for changing outbound traffic behavior,then the firewall and network topology is your best tool for controlling inbound traffic.
Opening inbound communication:
Bridged containers aren’t accessible from the host network by default.Containers are protected by your host’s firewall system.The default network topology provides no route from the host’s external interface to a container interface.That means there’s just no way to get to a container from outside the host .
The docker run command provides a flag, -p=[] or –publish=[],that you can create a mapping between a port on the host’s network stack and the new container’s interface.The format of mapping can have four forms:
<containerport> This form binds the container port to a dynamic port on all the host’s interface:
docker run –p 3333
<hostport>:<containerport> This form binds the specified container port to the specified port on each of the host’s interface:
docker run -p 3333:3333
<ip> : :<containerport> This form binds the container port to a dynamic port on the interface with the secified IP address:
docker run -p 192.168.0.32: :2222
<ip>:<hostport>:<containerport> This form binds the container port to the specified port on the interface with the specified IP address.
docker run -p 192.168.0.32:1111:1111
This examples assume that your host’s IP address is 192.168.0.32.This is arbitrary but useful to demonstrate the feature.Each of the command fragments will create a route from a port on a host interface to a specific port on the container interface.The different forms offer a renage of granularity and control.This flag is another that can be repeated as many times as you need to provide the desired set of mappings.
The docker run command provides an alternate way to accomplish opening channels.If you can accept a dynamic or ephemeral port assignment on the host,you can use the –p, or -publish-all,flag .This flag tells the Docker daemon to create mappings like the first form of the -p option for all ports that an image reports,to expose.
Images carry a list of ports that are exposed for simplicity and as a hint to users where contained services are listening. For example,if you know that an image like dockerinaction/ch_5 expose expose ports 5000,6000 and 7000,each of the following commands do the same thing.
docker run -d --name dawson \
-p 5000 \
-p 6000 \
-p 7000 \
dockerinaction/ch5_expose (expose all ports)
docker run -d --name woolery \
-P \
dockerinaction/ch5_expose (expose relevant ports)
The docker run command provides another flag, --expose,that takes a port number that the container should expose.This flag can be set multiple times,once for each port:
docker run -d --name philbin \
--expose 8000 \ (Expose another port)
-P \ (Publish all ports)
dockerinaction/ch5_expose
Using --expose in this way will add port 8000 to the list of ports that should be bound to dynamic ports using the -P flag.
After running this example,you can see what these ports were mapped to by docker ps,docker inspect,or a new command,docker port.
The port subcommand takes either the container name or ID is an argument and produces a simple list with one port map entry per line.
docker port philbin
Running this command should produce a list of the following :
---------------------------------------------------------------
5000/tcp -> 0.0.0.0:49164
.
.
----------------------------------------------------------------
We are able to manage routing any inbound traffic to the correct bridged container running on your host.
Inter-container communication:
All the containers we used so far use the docker bridge network to communicate with each other and the network that the host is on.All local bridged containers are on the same bridge network and can communicate with each other by default.
Following command demonstrates how containers can communicate over this network:
--------------------------------------------------------------------------------
docker run -it --rm dockerinaction/ch5_nmap -sS -p 3333 172.17.0.0/24
This command will run a program called nmap to scan all interfaces attached to the bridge network.In this case it’s looking for any interface that’s accepting connections on port 3333.If you had such a service running in another container ,this command would have discovered it and you could use another program to connect it.
Note:
When you start the docker daemon,you can configure it to disallow network connections between containers.You can achieve this by setting --icc=false when you start the docker daemon:
docker -d --icc=false……
(When inter-container communication is disable,any traffic from one container to another container will be blocked by the host’s firewall except where explicitly allowed. )
Modifying the bridge interface:
Docker provides three options for customizing the bridge interface that the docker daemon builds on first startup.These options let the user do the following:
Define the address and subnet of the bridge
Define the range of IP addresses that can be assigned to containers.
Define the maximum transmission unit (MTU).
To define the IP address of the bridge and the subnet range,use the --bip flag when you start the Docker daemon.There are all sorts of reasons why you might want to use a different IP range for your bridge network .When you encounter one of these situations,making the change is as simple as using one flag.
Using the --bip flag (which stands for bridge IP),you can set the IP address of the bridge interface that the docker will create and the size of the subnet using a classless inter-domain routing (CIDR) formatted address.CIDR notation provides a way to specify an IP address and and its routing prefix.
Suppose you want to set your bridge IP address to 192.168.0.128 and allocate the last 128 addresses in that subnet prefix to the bridge network.In that case,you would set the value of --bip to 192.168.0.128/25.To be explicit,using this value will create the dockero interface,set its ip address to 192.168.0.128 and allow IP addresses that range from 192.168.0.128 to 192.168.0.255.The command similar to this:
docker -d --bip “192.168.0.128”….
With a network defined for the bridge,you can go on to customize which IP addresses in that network can be assigned to new containers.To do so ,provide a similar CIDR notation description to the --fixed-cidr flag.
If you wanted to reserve only the last 64 addresses of the network assigned to the bridge interface,you would use 192.168.0.192/26.When the Docker Daemon is started with this set,new containers will receive an IP address between 192.168.0.192 and 192.168.0.255.The only cavet with this option is that range specified must be a subnet of the network assigned to the bridge.
docker -d --fixed-cidr “192.168.0.192/26”
Network interfaces have a limit to the maximum size of a packet size of 1500 bytes.This is the configured default.
In some specific instances you will need to change the MTU on the docker bridge.When you encounter such a scenario,you can use the --mtu flag to set the sizes in bytes:
docker -d -mtu 1200
Users who are more comfortable with linux networking primitives may like to know that can provide their own custom bridge interface instead of using the default bridge.To do so,configure your bridge interface and then tell the docker daemon to use it instead of dockero when you start the daemon.The flag to use is -b or –bridge .If you have configured a bridge named mybridge ,you could start docker with a command like the following.
docker -d -b mybridge …..
docker -d --bridge mybridge ….
Joined containers:
The next less isolated network container archetype is called a joined container.These containers share a common network stack .In this way there’s no isolation between joined containers.
Docker builds this type of container by providing access to the interfaces created for a specific container to another new container.Interfaces are in this way shared like managed volumes.
The easiest way to see joined containers in action is to use a special case and join it with a new container.The first command starts a server that listens on the loopback interface.The second command lists the open port created by the first command because both containers share the same network interface:
docker run -d --name brady \
--net none alpine:latest \
nc -1 127.0.0.1:3333
docker run -it \
--net container:brady
alpine:latest netstat -al
By running these two commands you create two containers that the share the same network interface.Because the first container is created as a closed container ,the two will only share that single loopback interface.The container values of the --net flag lets you specify the container that the new container should be joined with .Either the container name or its raw ID identifies the container that the new container should reuse.
Containers joined in this way will maintain other forms of isolation.They will maintain different file systems,different memory and so on.But they will have the exact same network components.That may sound concering,but this type of container can be useful.
Example:
In this last example you joined two containers on a network interface that has no access to the larger network.In doing so ,you expanded the usefulness of a closed container
Note:
You might use this pattern when two different programs with access to two different pieces of data need to communicate but shouldn’t share direct access to other’s data.Alternatively,you might use this pattern when you have network services that need to communicate but network access or service discovery mechanisms like DNS are unavailable.
When two containers are joined,all interfaces are shared and conflicts might happen on any of them.At first it might seem silly to join two containers that need bridge access.After all,they can already communicate over the docker bridge subnet .But consider situations where one process needs to monitor the other through otherwise protected channels.communication between containers is subject to firewall rules.If one process needs to communicate with another on an unexposed port.
Advantages of Joined containers:
Use joined containers when you want to use a single loopback interface for communication between programs in different containers.
Use joined containers if a program in one container is going to change the joined network stack and another program is going to use that modified network.
Use joined containers when you need to monitor the network traffic for a program in another container.
Open containers:
Open containers are dangerous .they have no network container and have full access to the host’s network.This include access to critical host services.Open containers provide absolutely no isolation and should be considered only in cases when you have no other option.
The only redeeming quality is that unprivileged containers are still unable to actually reconfigure the network stack.
This type of container is created when you specify host as the value of the --net option on the docker run command:
docker run --rm \
--net host \ (create an open container).
alpine:latest ip addr
Running this command will create a container from the latest alpine image and without any network jail.
When you execute ip addr inside the container,you can inspect all the host machine’s network interfaces.You should see several interfaces listed,including one named dockero.As you may have noticed,this example creates a container that executes a discrete task and then immediately removes the container.
Using this configuration,processes can bind to protected network ports numbered lower than 1024.
Inter container dependencies:
In this we need to learn how to use network software in one container from another.When you consider that the bridge network assigns IP addresses to containers dynamically at creation time,local service discovery can seem complicated.
One way to solve the problem would be to use a local DNS server and a registration hook when containers start.Another would be write your programs to scan the local networks for IP addresses listening on known ports.Both approaches handle dynamic environments but require a non-trivial workload and additional tooling.Each of these approaches will fail if arbitrary inter-container communications has been disabled .You could force all traffic out and back through the host’s interface to known published ports.
Introducing links for local service discovery:
When you create a new container,you can tell docker to link it to any other container.That target containers must be running when the new containers is created.The reason is simple.Containers hold their IP address only when they are running .If they are stopped,they lose that lease.
Adding a link on a new container does three things:
Environment variables describing the target containers end point will be created.
The link alias will be added to the DNS override list of the new container with the IP address of the target containers.
Most interestingly,if inter-container communication is disabled,Docker will add specific firewall rules to allow communication between linked containers.
The first two features of links are great for basic servive discovery,but the third feature enables users to harden their local container networks without sacrificing container-to-container communication.
The ports that are opened for communication are those that have been exposed by the target container.So the --expose flag provides a shortcut for only one particular type of container to host port mapping when ICC is enabled.when ICC is disabled,--expose becomes a tool for defining firewall rules and explicit declaration of a container’s interface on the network.In the same context,links become a more static dependencies.Here’s simple example;these images don’t actually exist.
docker run -d --name important data \ ( Named target of a link)
--expose 3306 \
dockerinaction /mysql_noauth \
service mysql_noauth start
docker run -d --name importantwebapp \ (create link and set alias todb)
--link importantdata:db \
dockerinaction/ch5_web startapp.sh -db tcp://db:3306
docker run -d --name buggyprogram \
dockerinaction/ch5_buggy (This container has no route to important data).
Link aliases:
Links are one-way network dependencies created when one container is created and specifies link to another.the --link flag used for this purpose takes a single argument.That argument is a map from a container name or ID to an alias.The alias can be anything as long as it’s unique in the scope of the container being created .So,if three containers named a,b and c already exists and are running,then I could run the following.
docker run --link a:alias-a --link b:alias-b --link c:alias-c …..
But if I made a mistake and assigned some or all containers to the same alias,then that alias would only contain connection information for one of the other containers.In this case,the firewall rules would still be created but would be nearly useless without that connection information.
Link aliases create a higher-level issue.Software running inside a container needs to know the alias of the container or host it’s connecting to so it can perform the lookup.Similar to host names,link aliases become a symbol that multiple parties must agree on for a system to operate correctly. Link aliases function as a contract.
A developer may build their application to assume that a database will be have an alias of “database” and always look for a it at tcp://database:3306 because a DNS override with that host name would exist.This expected host name approach would work as long as the person or process building the container either creates a link aliased to a database or uses –add-host to create the host name.Alternatively,the application could always look for connections information from the environment variable named DATABASE_PORT.The environment variable approach will work only when a link is created with that alias.
The trouble is that there are no dependency declarations or runtime dependency checks.It’s easy for the person building the container to do so without providing the required linkage.
Docker uses must either rely on documentation to communicate these dependencies or include custom dependency checking and fail fast behavior on container startup.I recommend building the dependency-checking code first.for example ,the following script is included in dockerinaction/ch5_ff to validate that a link named database has been set at startup:
#!/bin/sh
if (-z $(DATABASE_PORT+X) )
then
echo “links alias ‘database’ was not set !”
exit
exec “$@”
fi
You can see this script at work by running the following:
Docker run –d --name mydb --expose 3306 \
(create valid link target )
docker run -it --rm \
dockerinaction/ch5_ff echo this “shouldn’t” work.
(Test without link)
docker run –it --rm \
--link mydb:wrongalias \
dockerinaction/ch5_ff echo wrong
(Test with incorrect link alias)
docker run –it --rm \
--link mydb:database \
dockerinaction/ch5_ff echo It worked (Test correct alias)
docker stop mydb && docker rm mydb
(shutdown link target container)
This examples script relies on the environment modifications made by docker when links are created.
Environment modifications:
As above mentiones that creating a link will add connection information to a new container.This connection information is injected in the new container by adding environment variables and a host name mapping in the DNS override system.Let’s start with an example to inspect the link modifications.
Docker run -d --name mydb \
--expose 2222 --expose 3333 --expose 4444/udp \
alpine:latest nc -1 0.0.0.0:2222
(create valid link target)
docker run -it --rm \
--link mydb:database \
dockerinaction/ch5_ff env
(create link and list environment variables)
docker stop mydb && docker rm mydb
Output:
DATABASE_PORT=tcp://172.17.0.23:3333
.
.
.
.
These are a sample of environment variables created for a link.All variables relating to a specific link will use the link alias as a prefix.There will always be a single variable with the _NAME suffix that includes the name of the current container,a Slash and the link alias.For each port exposed by the linked container,there will be four individual environment variables with the exposed port in the variable name.
Link nature and shortcomings
The nature of links is such that dependencies are directional,static and nontransitive.Non-transitive means that linked containers wont inherit links.More explicitly,If I link container B to container A and then link container C to container A.
Links work by determining the network information of a container (IP address and exposed ports) and then injecting that into a new container.Because this is done at container creation time and Docker cannot know what a container’s IP address will be before that container is running,links can only be built from new containers to existing containers.This is not to say that communication is one way but rather that discovery is one way.this also means that if a dependency stops for some reason.the link will be broken.Remember that containers maintain IP address leases only when they are running,So if a container is stopped or restarted,it will lose its IP lease and any linked containers will have stale data.
This property has caused some to criticize the values of links.The issue is that the deeper a dependency fails,the greater the domino effect of required container restarts.This might be an issue for some,but you must consider the specific impact.
If a critical service like a database fails,an availability event has already occurred.The chosen service discovery method impacts the recovery routine.An unavailable service might recover on the same or different IP address.Links will break only if the IP address changes and wffill require restarts.This leads some to jump to more dynamic lookup systems like DNS
This comment has been removed by the author.
ReplyDelete