Implement allocating IPs from CIDR within bridge network
This commit is contained in:
unclejack 2014-09-22 17:09:16 +03:00
commit 9fb34ae571
8 changed files with 202 additions and 79 deletions

View File

@ -30,6 +30,7 @@ type Config struct {
DefaultIp net.IP DefaultIp net.IP
BridgeIface string BridgeIface string
BridgeIP string BridgeIP string
FixedCIDR string
InterContainerCommunication bool InterContainerCommunication bool
GraphDriver string GraphDriver string
GraphOptions []string GraphOptions []string
@ -53,6 +54,7 @@ func (config *Config) InstallFlags() {
flag.BoolVar(&config.EnableIpMasq, []string{"-ip-masq"}, true, "Enable IP masquerading for bridge's IP range") flag.BoolVar(&config.EnableIpMasq, []string{"-ip-masq"}, true, "Enable IP masquerading for bridge's IP range")
flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b") flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b")
flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking") flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
flag.StringVar(&config.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)\nthis subnet must be nested in the bridge subnet (which is defined by -b or --bip)")
flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication") flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication")
flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver") flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver") flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver")

View File

@ -821,6 +821,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
job.SetenvBool("EnableIpMasq", config.EnableIpMasq) job.SetenvBool("EnableIpMasq", config.EnableIpMasq)
job.Setenv("BridgeIface", config.BridgeIface) job.Setenv("BridgeIface", config.BridgeIface)
job.Setenv("BridgeIP", config.BridgeIP) job.Setenv("BridgeIP", config.BridgeIP)
job.Setenv("FixedCIDR", config.FixedCIDR)
job.Setenv("DefaultBindingIP", config.DefaultIp.String()) job.Setenv("DefaultBindingIP", config.DefaultIp.String())
if err := job.Run(); err != nil { if err := job.Run(); err != nil {

View File

@ -84,6 +84,7 @@ func InitDriver(job *engine.Job) engine.Status {
ipMasq = job.GetenvBool("EnableIpMasq") ipMasq = job.GetenvBool("EnableIpMasq")
ipForward = job.GetenvBool("EnableIpForward") ipForward = job.GetenvBool("EnableIpForward")
bridgeIP = job.Getenv("BridgeIP") bridgeIP = job.Getenv("BridgeIP")
fixedCIDR = job.Getenv("FixedCIDR")
) )
if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" { if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" {
@ -155,6 +156,16 @@ func InitDriver(job *engine.Job) engine.Status {
} }
bridgeNetwork = network bridgeNetwork = network
if fixedCIDR != "" {
_, subnet, err := net.ParseCIDR(fixedCIDR)
if err != nil {
return job.Error(err)
}
log.Debugf("Subnet: %v", subnet)
if err := ipallocator.RegisterSubnet(bridgeNetwork, subnet); err != nil {
return job.Error(err)
}
}
// https://github.com/docker/docker/issues/2768 // https://github.com/docker/docker/issues/2768
job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", bridgeNetwork.IP) job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", bridgeNetwork.IP)
@ -318,14 +329,14 @@ func createBridgeIface(name string) error {
// Allocate a network interface // Allocate a network interface
func Allocate(job *engine.Job) engine.Status { func Allocate(job *engine.Job) engine.Status {
var ( var (
ip *net.IP ip net.IP
err error err error
id = job.Args[0] id = job.Args[0]
requestedIP = net.ParseIP(job.Getenv("RequestedIP")) requestedIP = net.ParseIP(job.Getenv("RequestedIP"))
) )
if requestedIP != nil { if requestedIP != nil {
ip, err = ipallocator.RequestIP(bridgeNetwork, &requestedIP) ip, err = ipallocator.RequestIP(bridgeNetwork, requestedIP)
} else { } else {
ip, err = ipallocator.RequestIP(bridgeNetwork, nil) ip, err = ipallocator.RequestIP(bridgeNetwork, nil)
} }
@ -343,7 +354,7 @@ func Allocate(job *engine.Job) engine.Status {
out.SetInt("IPPrefixLen", size) out.SetInt("IPPrefixLen", size)
currentInterfaces.Set(id, &networkInterface{ currentInterfaces.Set(id, &networkInterface{
IP: *ip, IP: ip,
}) })
out.WriteTo(job.Stdout) out.WriteTo(job.Stdout)
@ -368,7 +379,7 @@ func Release(job *engine.Job) engine.Status {
} }
} }
if err := ipallocator.ReleaseIP(bridgeNetwork, &containerInterface.IP); err != nil { if err := ipallocator.ReleaseIP(bridgeNetwork, containerInterface.IP); err != nil {
log.Infof("Unable to release ip %s", err) log.Infof("Unable to release ip %s", err)
} }
return engine.StatusOK return engine.StatusOK

View File

@ -3,26 +3,39 @@ package ipallocator
import ( import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"github.com/docker/docker/daemon/networkdriver"
"net" "net"
"sync" "sync"
"github.com/docker/docker/daemon/networkdriver"
) )
// allocatedMap is thread-unsafe set of allocated IP // allocatedMap is thread-unsafe set of allocated IP
type allocatedMap struct { type allocatedMap struct {
p map[int32]struct{} p map[uint32]struct{}
last int32 last uint32
begin uint32
end uint32
} }
func newAllocatedMap() *allocatedMap { func newAllocatedMap(network *net.IPNet) *allocatedMap {
return &allocatedMap{p: make(map[int32]struct{})} firstIP, lastIP := networkdriver.NetworkRange(network)
begin := ipToInt(firstIP) + 2
end := ipToInt(lastIP) - 1
return &allocatedMap{
p: make(map[uint32]struct{}),
begin: begin,
end: end,
last: begin - 1, // so first allocated will be begin
}
} }
type networkSet map[string]*allocatedMap type networkSet map[string]*allocatedMap
var ( var (
ErrNoAvailableIPs = errors.New("no available ip addresses on network") ErrNoAvailableIPs = errors.New("no available ip addresses on network")
ErrIPAlreadyAllocated = errors.New("ip already allocated") ErrIPAlreadyAllocated = errors.New("ip already allocated")
ErrNetworkAlreadyRegistered = errors.New("network already registered")
ErrBadSubnet = errors.New("network not contains specified subnet")
) )
var ( var (
@ -30,47 +43,63 @@ var (
allocatedIPs = networkSet{} allocatedIPs = networkSet{}
) )
// RegisterSubnet registers network in global allocator with bounds
// defined by subnet. If you want to use network range you must call
// this method before first RequestIP, otherwise full network range will be used
func RegisterSubnet(network *net.IPNet, subnet *net.IPNet) error {
lock.Lock()
defer lock.Unlock()
key := network.String()
if _, ok := allocatedIPs[key]; ok {
return ErrNetworkAlreadyRegistered
}
n := newAllocatedMap(network)
beginIP, endIP := networkdriver.NetworkRange(subnet)
begin, end := ipToInt(beginIP)+1, ipToInt(endIP)-1
if !(begin >= n.begin && end <= n.end && begin < end) {
return ErrBadSubnet
}
n.begin = begin
n.end = end
n.last = begin - 1
allocatedIPs[key] = n
return nil
}
// RequestIP requests an available ip from the given network. It // RequestIP requests an available ip from the given network. It
// will return the next available ip if the ip provided is nil. If the // will return the next available ip if the ip provided is nil. If the
// ip provided is not nil it will validate that the provided ip is available // ip provided is not nil it will validate that the provided ip is available
// for use or return an error // for use or return an error
func RequestIP(network *net.IPNet, ip *net.IP) (*net.IP, error) { func RequestIP(network *net.IPNet, ip net.IP) (net.IP, error) {
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
key := network.String() key := network.String()
allocated, ok := allocatedIPs[key] allocated, ok := allocatedIPs[key]
if !ok { if !ok {
allocated = newAllocatedMap() allocated = newAllocatedMap(network)
allocatedIPs[key] = allocated allocatedIPs[key] = allocated
} }
if ip == nil { if ip == nil {
return allocated.getNextIP(network) return allocated.getNextIP()
} }
return allocated.checkIP(network, ip) return allocated.checkIP(ip)
} }
// ReleaseIP adds the provided ip back into the pool of // ReleaseIP adds the provided ip back into the pool of
// available ips to be returned for use. // available ips to be returned for use.
func ReleaseIP(network *net.IPNet, ip *net.IP) error { func ReleaseIP(network *net.IPNet, ip net.IP) error {
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
if allocated, exists := allocatedIPs[network.String()]; exists { if allocated, exists := allocatedIPs[network.String()]; exists {
pos := getPosition(network, ip) pos := ipToInt(ip)
delete(allocated.p, pos) delete(allocated.p, pos)
} }
return nil return nil
} }
// convert the ip into the position in the subnet. Only func (allocated *allocatedMap) checkIP(ip net.IP) (net.IP, error) {
// position are saved in the set pos := ipToInt(ip)
func getPosition(network *net.IPNet, ip *net.IP) int32 {
first, _ := networkdriver.NetworkRange(network)
return ipToInt(ip) - ipToInt(&first)
}
func (allocated *allocatedMap) checkIP(network *net.IPNet, ip *net.IP) (*net.IP, error) {
pos := getPosition(network, ip)
if _, ok := allocated.p[pos]; ok { if _, ok := allocated.p[pos]; ok {
return nil, ErrIPAlreadyAllocated return nil, ErrIPAlreadyAllocated
} }
@ -81,47 +110,30 @@ func (allocated *allocatedMap) checkIP(network *net.IPNet, ip *net.IP) (*net.IP,
// return an available ip if one is currently available. If not, // return an available ip if one is currently available. If not,
// return the next available ip for the nextwork // return the next available ip for the nextwork
func (allocated *allocatedMap) getNextIP(network *net.IPNet) (*net.IP, error) { func (allocated *allocatedMap) getNextIP() (net.IP, error) {
var ( for pos := allocated.last + 1; pos != allocated.last; pos++ {
ownIP = ipToInt(&network.IP) if pos > allocated.end {
first, _ = networkdriver.NetworkRange(network) pos = allocated.begin
base = ipToInt(&first)
size = int(networkdriver.NetworkSize(network.Mask))
max = int32(size - 2) // size -1 for the broadcast network, -1 for the gateway network
pos = allocated.last
)
var (
firstNetIP = network.IP.To4().Mask(network.Mask)
firstAsInt = ipToInt(&firstNetIP) + 1
)
for i := int32(0); i < max; i++ {
pos = pos%max + 1
next := int32(base + pos)
if next == ownIP || next == firstAsInt {
continue
} }
if _, ok := allocated.p[pos]; ok { if _, ok := allocated.p[pos]; ok {
continue continue
} }
allocated.p[pos] = struct{}{} allocated.p[pos] = struct{}{}
allocated.last = pos allocated.last = pos
return intToIP(next), nil return intToIP(pos), nil
} }
return nil, ErrNoAvailableIPs return nil, ErrNoAvailableIPs
} }
// Converts a 4 bytes IP into a 32 bit integer // Converts a 4 bytes IP into a 32 bit integer
func ipToInt(ip *net.IP) int32 { func ipToInt(ip net.IP) uint32 {
return int32(binary.BigEndian.Uint32(ip.To4())) return binary.BigEndian.Uint32(ip.To4())
} }
// Converts 32 bit integer into a 4 bytes IP address // Converts 32 bit integer into a 4 bytes IP address
func intToIP(n int32) *net.IP { func intToIP(n uint32) net.IP {
b := make([]byte, 4) b := make([]byte, 4)
binary.BigEndian.PutUint32(b, uint32(n)) binary.BigEndian.PutUint32(b, n)
ip := net.IP(b) ip := net.IP(b)
return &ip return ip
} }

View File

@ -17,7 +17,7 @@ func TestRequestNewIps(t *testing.T) {
Mask: []byte{255, 255, 255, 0}, Mask: []byte{255, 255, 255, 0},
} }
var ip *net.IP var ip net.IP
var err error var err error
for i := 2; i < 10; i++ { for i := 2; i < 10; i++ {
ip, err = RequestIP(network, nil) ip, err = RequestIP(network, nil)
@ -106,19 +106,19 @@ func TestRequesetSpecificIp(t *testing.T) {
ip := net.ParseIP("192.168.1.5") ip := net.ParseIP("192.168.1.5")
if _, err := RequestIP(network, &ip); err != nil { if _, err := RequestIP(network, ip); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestConversion(t *testing.T) { func TestConversion(t *testing.T) {
ip := net.ParseIP("127.0.0.1") ip := net.ParseIP("127.0.0.1")
i := ipToInt(&ip) i := ipToInt(ip)
if i == 0 { if i == 0 {
t.Fatal("converted to zero") t.Fatal("converted to zero")
} }
conv := intToIP(i) conv := intToIP(i)
if !ip.Equal(*conv) { if !ip.Equal(conv) {
t.Error(conv.String()) t.Error(conv.String())
} }
} }
@ -146,7 +146,7 @@ func TestIPAllocator(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
assertIPEquals(t, &expectedIPs[i], ip) assertIPEquals(t, expectedIPs[i], ip)
} }
// Before loop begin // Before loop begin
// 2(f) - 3(f) - 4(f) - 5(f) - 6(f) // 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
@ -179,19 +179,19 @@ func TestIPAllocator(t *testing.T) {
} }
// Release some IPs in non-sequential order // Release some IPs in non-sequential order
if err := ReleaseIP(network, &expectedIPs[3]); err != nil { if err := ReleaseIP(network, expectedIPs[3]); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// 2(u) - 3(u) - 4(u) - 5(f) - 6(u) // 2(u) - 3(u) - 4(u) - 5(f) - 6(u)
// ↑ // ↑
if err := ReleaseIP(network, &expectedIPs[2]); err != nil { if err := ReleaseIP(network, expectedIPs[2]); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// 2(u) - 3(u) - 4(f) - 5(f) - 6(u) // 2(u) - 3(u) - 4(f) - 5(f) - 6(u)
// ↑ // ↑
if err := ReleaseIP(network, &expectedIPs[4]); err != nil { if err := ReleaseIP(network, expectedIPs[4]); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// 2(u) - 3(u) - 4(f) - 5(f) - 6(f) // 2(u) - 3(u) - 4(f) - 5(f) - 6(f)
@ -199,7 +199,7 @@ func TestIPAllocator(t *testing.T) {
// Make sure that IPs are reused in sequential order, starting // Make sure that IPs are reused in sequential order, starting
// with the first released IP // with the first released IP
newIPs := make([]*net.IP, 3) newIPs := make([]net.IP, 3)
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
ip, err := RequestIP(network, nil) ip, err := RequestIP(network, nil)
if err != nil { if err != nil {
@ -208,9 +208,9 @@ func TestIPAllocator(t *testing.T) {
newIPs[i] = ip newIPs[i] = ip
} }
assertIPEquals(t, &expectedIPs[2], newIPs[0]) assertIPEquals(t, expectedIPs[2], newIPs[0])
assertIPEquals(t, &expectedIPs[3], newIPs[1]) assertIPEquals(t, expectedIPs[3], newIPs[1])
assertIPEquals(t, &expectedIPs[4], newIPs[2]) assertIPEquals(t, expectedIPs[4], newIPs[2])
_, err = RequestIP(network, nil) _, err = RequestIP(network, nil)
if err == nil { if err == nil {
@ -226,7 +226,7 @@ func TestAllocateFirstIP(t *testing.T) {
} }
firstIP := network.IP.To4().Mask(network.Mask) firstIP := network.IP.To4().Mask(network.Mask)
first := ipToInt(&firstIP) + 1 first := ipToInt(firstIP) + 1
ip, err := RequestIP(network, nil) ip, err := RequestIP(network, nil)
if err != nil { if err != nil {
@ -247,7 +247,7 @@ func TestAllocateAllIps(t *testing.T) {
} }
var ( var (
current, first *net.IP current, first net.IP
err error err error
isFirst = true isFirst = true
) )
@ -313,14 +313,94 @@ func TestAllocateDifferentSubnets(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assertIPEquals(t, &expectedIPs[0], ip11) assertIPEquals(t, expectedIPs[0], ip11)
assertIPEquals(t, &expectedIPs[1], ip12) assertIPEquals(t, expectedIPs[1], ip12)
assertIPEquals(t, &expectedIPs[2], ip21) assertIPEquals(t, expectedIPs[2], ip21)
assertIPEquals(t, &expectedIPs[3], ip22) assertIPEquals(t, expectedIPs[3], ip22)
}
func TestRegisterBadTwice(t *testing.T) {
defer reset()
network := &net.IPNet{
IP: []byte{192, 168, 1, 1},
Mask: []byte{255, 255, 255, 0},
}
subnet := &net.IPNet{
IP: []byte{192, 168, 1, 8},
Mask: []byte{255, 255, 255, 248},
}
if err := RegisterSubnet(network, subnet); err != nil {
t.Fatal(err)
}
subnet = &net.IPNet{
IP: []byte{192, 168, 1, 16},
Mask: []byte{255, 255, 255, 248},
}
if err := RegisterSubnet(network, subnet); err != ErrNetworkAlreadyRegistered {
t.Fatalf("Expecteded ErrNetworkAlreadyRegistered error, got %v", err)
}
} }
func assertIPEquals(t *testing.T, ip1, ip2 *net.IP) { func TestRegisterBadRange(t *testing.T) {
if !ip1.Equal(*ip2) { defer reset()
network := &net.IPNet{
IP: []byte{192, 168, 1, 1},
Mask: []byte{255, 255, 255, 0},
}
subnet := &net.IPNet{
IP: []byte{192, 168, 1, 1},
Mask: []byte{255, 255, 0, 0},
}
if err := RegisterSubnet(network, subnet); err != ErrBadSubnet {
t.Fatalf("Expected ErrBadSubnet error, got %v", err)
}
}
func TestAllocateFromRange(t *testing.T) {
defer reset()
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
// 192.168.1.9 - 192.168.1.14
subnet := &net.IPNet{
IP: []byte{192, 168, 0, 8},
Mask: []byte{255, 255, 255, 248},
}
if err := RegisterSubnet(network, subnet); err != nil {
t.Fatal(err)
}
expectedIPs := []net.IP{
0: net.IPv4(192, 168, 0, 9),
1: net.IPv4(192, 168, 0, 10),
2: net.IPv4(192, 168, 0, 11),
3: net.IPv4(192, 168, 0, 12),
4: net.IPv4(192, 168, 0, 13),
5: net.IPv4(192, 168, 0, 14),
}
for _, ip := range expectedIPs {
rip, err := RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, ip, rip)
}
if _, err := RequestIP(network, nil); err != ErrNoAvailableIPs {
t.Fatalf("Expected ErrNoAvailableIPs error, got %v", err)
}
for _, ip := range expectedIPs {
ReleaseIP(network, ip)
rip, err := RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, ip, rip)
}
}
func assertIPEquals(t *testing.T, ip1, ip2 net.IP) {
if !ip1.Equal(ip2) {
t.Fatalf("Expected IP %s, got %s", ip1, ip2) t.Fatalf("Expected IP %s, got %s", ip1, ip2)
} }
} }

View File

@ -49,6 +49,10 @@ unix://[/path/to/socket] to use.
**-g**="" **-g**=""
Path to use as the root of the Docker runtime. Default is `/var/lib/docker`. Path to use as the root of the Docker runtime. Default is `/var/lib/docker`.
**--fixed-cidr**=""
IPv4 subnet for fixed IPs (ex: 10.20.0.0/16); this subnet must be nested in the bridge subnet (which is defined by \-b or \-\-bip)
**--icc**=*true*|*false* **--icc**=*true*|*false*
Enable inter\-container communication. Default is true. Enable inter\-container communication. Default is true.

View File

@ -54,6 +54,9 @@ server when it starts up, and cannot be changed once it is running:
* `--bip=CIDR` — see * `--bip=CIDR` — see
[Customizing docker0](#docker0) [Customizing docker0](#docker0)
* `--fixed-cidr` — see
[Customizing docker0](#docker0)
* `-H SOCKET...` or `--host=SOCKET...` * `-H SOCKET...` or `--host=SOCKET...`
This might sound like it would affect container networking, This might sound like it would affect container networking,
but it actually faces in the other direction: but it actually faces in the other direction:
@ -365,17 +368,25 @@ By default, the Docker server creates and configures the host system's
can pass packets back and forth between other physical or virtual can pass packets back and forth between other physical or virtual
network interfaces so that they behave as a single Ethernet network. network interfaces so that they behave as a single Ethernet network.
Docker configures `docker0` with an IP address and netmask so the host Docker configures `docker0` with an IP address, netmask and IP
machine can both receive and send packets to containers connected to the allocation range. The host machine can both receive and send packets to
bridge, and gives it an MTU — the *maximum transmission unit* or largest containers connected to the bridge, and gives it an MTU — the *maximum
packet length that the interface will allow — of either 1,500 bytes or transmission unit* or largest packet length that the interface will
else a more specific value copied from the Docker host's interface that allow — of either 1,500 bytes or else a more specific value copied from
supports its default route. Both are configurable at server startup: the Docker host's interface that supports its default route. These
options are configurable at server startup:
* `--bip=CIDR` — supply a specific IP address and netmask for the * `--bip=CIDR` — supply a specific IP address and netmask for the
`docker0` bridge, using standard CIDR notation like `docker0` bridge, using standard CIDR notation like
`192.168.1.5/24`. `192.168.1.5/24`.
* `--fixed-cidr=CIDR` — restrict the IP range from the `docker0` subnet,
using the standard CIDR notation like `172.167.1.0/28`. This range must
be and IPv4 range for fixed IPs (ex: 10.20.0.0/16) and must be a subset
of the bridge IP range (`docker0` or set using `--bridge`). For example
with `--fixed-cidr=192.168.1.0/25`, IPs for your containers will be chosen
from the first half of `192.168.1.0/24` subnet.
* `--mtu=BYTES` — override the maximum packet length on `docker0`. * `--mtu=BYTES` — override the maximum packet length on `docker0`.
On Ubuntu you would add these to the `DOCKER_OPTS` setting in On Ubuntu you would add these to the `DOCKER_OPTS` setting in

View File

@ -54,6 +54,8 @@ expect an integer, and they can only be specified once.
-b, --bridge="" Attach containers to a pre-existing network bridge -b, --bridge="" Attach containers to a pre-existing network bridge
use 'none' to disable container networking use 'none' to disable container networking
--bip="" Use this CIDR notation address for the network bridge's IP, not compatible with -b --bip="" Use this CIDR notation address for the network bridge's IP, not compatible with -b
--fixed-cidr="" IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)
this subnet must be nested in the bridge subnet (which is defined by -b or --bip)
-D, --debug=false Enable debug mode -D, --debug=false Enable debug mode
-d, --daemon=false Enable daemon mode -d, --daemon=false Enable daemon mode
--dns=[] Force Docker to use specific DNS servers --dns=[] Force Docker to use specific DNS servers