Sunday, June 11, 2017

Docker Network Exposure

Network exposure:

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:

172.17.0.45     mycontainer
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

1 comment: