Stable Networking: Keep the same network settings during the entire container lifecycle.
| ... | ... |
@@ -441,7 +441,7 @@ func (container *Container) buildHostnameAndHostsFiles(IP string) error {
|
| 441 | 441 |
return container.buildHostsFiles(IP) |
| 442 | 442 |
} |
| 443 | 443 |
|
| 444 |
-func (container *Container) allocateNetwork() error {
|
|
| 444 |
+func (container *Container) AllocateNetwork() (err error) {
|
|
| 445 | 445 |
mode := container.hostConfig.NetworkMode |
| 446 | 446 |
if container.Config.NetworkDisabled || !mode.IsPrivate() {
|
| 447 | 447 |
return nil |
| ... | ... |
@@ -449,7 +449,6 @@ func (container *Container) allocateNetwork() error {
|
| 449 | 449 |
|
| 450 | 450 |
var ( |
| 451 | 451 |
env *engine.Env |
| 452 |
- err error |
|
| 453 | 452 |
eng = container.daemon.eng |
| 454 | 453 |
) |
| 455 | 454 |
|
| ... | ... |
@@ -461,6 +460,15 @@ func (container *Container) allocateNetwork() error {
|
| 461 | 461 |
return err |
| 462 | 462 |
} |
| 463 | 463 |
|
| 464 |
+ // Error handling: At this point, the interface is allocated so we have to |
|
| 465 |
+ // make sure that it is always released in case of error, otherwise we |
|
| 466 |
+ // might leak resources. |
|
| 467 |
+ defer func() {
|
|
| 468 |
+ if err != nil {
|
|
| 469 |
+ eng.Job("release_interface", container.ID).Run()
|
|
| 470 |
+ } |
|
| 471 |
+ }() |
|
| 472 |
+ |
|
| 464 | 473 |
if container.Config.PortSpecs != nil {
|
| 465 | 474 |
if err := migratePortMappings(container.Config, container.hostConfig); err != nil {
|
| 466 | 475 |
return err |
| ... | ... |
@@ -511,7 +519,7 @@ func (container *Container) allocateNetwork() error {
|
| 511 | 511 |
return nil |
| 512 | 512 |
} |
| 513 | 513 |
|
| 514 |
-func (container *Container) releaseNetwork() {
|
|
| 514 |
+func (container *Container) ReleaseNetwork() {
|
|
| 515 | 515 |
if container.Config.NetworkDisabled {
|
| 516 | 516 |
return |
| 517 | 517 |
} |
| ... | ... |
@@ -521,11 +529,41 @@ func (container *Container) releaseNetwork() {
|
| 521 | 521 |
container.NetworkSettings = &NetworkSettings{}
|
| 522 | 522 |
} |
| 523 | 523 |
|
| 524 |
+func (container *Container) isNetworkAllocated() bool {
|
|
| 525 |
+ return container.NetworkSettings.IPAddress != "" |
|
| 526 |
+} |
|
| 527 |
+ |
|
| 528 |
+func (container *Container) RestoreNetwork() error {
|
|
| 529 |
+ mode := container.hostConfig.NetworkMode |
|
| 530 |
+ // Don't attempt a restore if we previously didn't allocate networking. |
|
| 531 |
+ // This might be a legacy container with no network allocated, in which case the |
|
| 532 |
+ // allocation will happen once and for all at start. |
|
| 533 |
+ if !container.isNetworkAllocated() || container.Config.NetworkDisabled || !mode.IsPrivate() {
|
|
| 534 |
+ return nil |
|
| 535 |
+ } |
|
| 536 |
+ |
|
| 537 |
+ eng := container.daemon.eng |
|
| 538 |
+ |
|
| 539 |
+ // Re-allocate the interface with the same IP and MAC address. |
|
| 540 |
+ job := eng.Job("allocate_interface", container.ID)
|
|
| 541 |
+ job.Setenv("RequestedIP", container.NetworkSettings.IPAddress)
|
|
| 542 |
+ job.Setenv("RequestedMac", container.NetworkSettings.MacAddress)
|
|
| 543 |
+ if err := job.Run(); err != nil {
|
|
| 544 |
+ return err |
|
| 545 |
+ } |
|
| 546 |
+ |
|
| 547 |
+ // Re-allocate any previously allocated ports. |
|
| 548 |
+ for port := range container.NetworkSettings.Ports {
|
|
| 549 |
+ if err := container.allocatePort(eng, port, container.NetworkSettings.Ports); err != nil {
|
|
| 550 |
+ return err |
|
| 551 |
+ } |
|
| 552 |
+ } |
|
| 553 |
+ return nil |
|
| 554 |
+} |
|
| 555 |
+ |
|
| 524 | 556 |
// cleanup releases any network resources allocated to the container along with any rules |
| 525 | 557 |
// around how containers are linked together. It also unmounts the container's root filesystem. |
| 526 | 558 |
func (container *Container) cleanup() {
|
| 527 |
- container.releaseNetwork() |
|
| 528 |
- |
|
| 529 | 559 |
// Disable all active links |
| 530 | 560 |
if container.activeLinks != nil {
|
| 531 | 561 |
for _, link := range container.activeLinks {
|
| ... | ... |
@@ -969,8 +1007,14 @@ func (container *Container) initializeNetworking() error {
|
| 969 | 969 |
container.Config.NetworkDisabled = true |
| 970 | 970 |
return container.buildHostnameAndHostsFiles("127.0.1.1")
|
| 971 | 971 |
} |
| 972 |
- if err := container.allocateNetwork(); err != nil {
|
|
| 973 |
- return err |
|
| 972 |
+ // Backward compatibility: |
|
| 973 |
+ // Network allocation used to be done when containers started, not when they |
|
| 974 |
+ // were created, therefore we might be starting a legacy container that |
|
| 975 |
+ // doesn't have networking. |
|
| 976 |
+ if !container.isNetworkAllocated() {
|
|
| 977 |
+ if err := container.AllocateNetwork(); err != nil {
|
|
| 978 |
+ return err |
|
| 979 |
+ } |
|
| 974 | 980 |
} |
| 975 | 981 |
return container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress) |
| 976 | 982 |
} |
| ... | ... |
@@ -1149,7 +1193,6 @@ func (container *Container) allocatePort(eng *engine.Engine, port nat.Port, bind |
| 1149 | 1149 |
return err |
| 1150 | 1150 |
} |
| 1151 | 1151 |
if err := job.Run(); err != nil {
|
| 1152 |
- eng.Job("release_interface", container.ID).Run()
|
|
| 1153 | 1152 |
return err |
| 1154 | 1153 |
} |
| 1155 | 1154 |
b.HostIp = portEnv.Get("HostIP")
|
| ... | ... |
@@ -83,6 +83,9 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos |
| 83 | 83 |
if container, err = daemon.newContainer(name, config, img); err != nil {
|
| 84 | 84 |
return nil, nil, err |
| 85 | 85 |
} |
| 86 |
+ if err := daemon.Register(container); err != nil {
|
|
| 87 |
+ return nil, nil, err |
|
| 88 |
+ } |
|
| 86 | 89 |
if err := daemon.createRootfs(container, img); err != nil {
|
| 87 | 90 |
return nil, nil, err |
| 88 | 91 |
} |
| ... | ... |
@@ -90,12 +93,13 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos |
| 90 | 90 |
if err := daemon.setHostConfig(container, hostConfig); err != nil {
|
| 91 | 91 |
return nil, nil, err |
| 92 | 92 |
} |
| 93 |
+ // We may only allocate the network if a host config was passed, otherwise we'll miss port mappings. |
|
| 94 |
+ if err := container.AllocateNetwork(); err != nil {
|
|
| 95 |
+ return nil, nil, err |
|
| 96 |
+ } |
|
| 93 | 97 |
} |
| 94 | 98 |
if err := container.ToDisk(); err != nil {
|
| 95 | 99 |
return nil, nil, err |
| 96 | 100 |
} |
| 97 |
- if err := daemon.Register(container); err != nil {
|
|
| 98 |
- return nil, nil, err |
|
| 99 |
- } |
|
| 100 | 101 |
return container, warnings, nil |
| 101 | 102 |
} |
| ... | ... |
@@ -372,6 +372,16 @@ func (daemon *Daemon) restore() error {
|
| 372 | 372 |
registeredContainers = append(registeredContainers, container) |
| 373 | 373 |
} |
| 374 | 374 |
|
| 375 |
+ // Restore networking of registered containers. |
|
| 376 |
+ // This must be performed prior to any IP allocation, otherwise we might |
|
| 377 |
+ // end up giving away an already allocated address. |
|
| 378 |
+ for _, container := range registeredContainers {
|
|
| 379 |
+ if err := container.RestoreNetwork(); err != nil {
|
|
| 380 |
+ log.Errorf("Failed to restore network for %v: %v", container.Name, err)
|
|
| 381 |
+ continue |
|
| 382 |
+ } |
|
| 383 |
+ } |
|
| 384 |
+ |
|
| 375 | 385 |
// check the restart policy on the containers and restart any container with |
| 376 | 386 |
// the restart policy of "always" |
| 377 | 387 |
if daemon.config.AutoRestart {
|
| ... | ... |
@@ -94,6 +94,8 @@ func (daemon *Daemon) Destroy(container *Container) error {
|
| 94 | 94 |
return err |
| 95 | 95 |
} |
| 96 | 96 |
|
| 97 |
+ container.ReleaseNetwork() |
|
| 98 |
+ |
|
| 97 | 99 |
// Deregister the container before removing its directory, to avoid race conditions |
| 98 | 100 |
daemon.idIndex.Delete(container.ID) |
| 99 | 101 |
daemon.containers.Delete(container.ID) |
| ... | ... |
@@ -1868,57 +1868,98 @@ func TestRunMutableNetworkFiles(t *testing.T) {
|
| 1868 | 1868 |
} |
| 1869 | 1869 |
} |
| 1870 | 1870 |
|
| 1871 |
-func TestRunHostsLinkedContainerUpdate(t *testing.T) {
|
|
| 1872 |
- deleteAllContainers() |
|
| 1873 |
- out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "c1", "busybox", "sh", "-c", "while true; do sleep 1; done")) |
|
| 1874 |
- if err != nil {
|
|
| 1875 |
- t.Fatal(err, out) |
|
| 1871 |
+func TestRunStableIPAndPort(t *testing.T) {
|
|
| 1872 |
+ const nContainers = 2 |
|
| 1873 |
+ var ids, ips, macs, ports [nContainers]string |
|
| 1874 |
+ |
|
| 1875 |
+ // Setup: Create a couple of containers and collect their IPs and public ports. |
|
| 1876 |
+ for i := 0; i < nContainers; i++ {
|
|
| 1877 |
+ runCmd := exec.Command(dockerBinary, "run", "-d", "-p", "1234", "busybox", "top") |
|
| 1878 |
+ out, _, err := runCommandWithOutput(runCmd) |
|
| 1879 |
+ if err != nil {
|
|
| 1880 |
+ t.Fatal(err) |
|
| 1881 |
+ } |
|
| 1882 |
+ ids[i] = strings.TrimSpace(out) |
|
| 1883 |
+ |
|
| 1884 |
+ ips[i], err = inspectField(ids[i], "NetworkSettings.IPAddress") |
|
| 1885 |
+ errorOut(err, t, out) |
|
| 1886 |
+ if ips[i] == "" {
|
|
| 1887 |
+ t.Fatal("IP allocation failed")
|
|
| 1888 |
+ } |
|
| 1889 |
+ |
|
| 1890 |
+ macs[i], err = inspectField(ids[i], "NetworkSettings.MacAddress") |
|
| 1891 |
+ errorOut(err, t, out) |
|
| 1892 |
+ |
|
| 1893 |
+ portCmd := exec.Command(dockerBinary, "port", ids[i], "1234") |
|
| 1894 |
+ ports[i], _, err = runCommandWithOutput(portCmd) |
|
| 1895 |
+ errorOut(err, t, out) |
|
| 1896 |
+ if ports[i] == "" {
|
|
| 1897 |
+ t.Fatal("Port allocation failed")
|
|
| 1898 |
+ } |
|
| 1876 | 1899 |
} |
| 1877 | 1900 |
|
| 1878 |
- // TODO fix docker cp and /etc/hosts |
|
| 1879 |
- out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--link", "c1:c1", "--name", "c2", "busybox", "sh", "-c", "while true;do sleep 1; done")) |
|
| 1880 |
- if err != nil {
|
|
| 1881 |
- t.Fatal(err, out) |
|
| 1901 |
+ // Stop them all. |
|
| 1902 |
+ for _, id := range ids {
|
|
| 1903 |
+ cmd := exec.Command(dockerBinary, "stop", id) |
|
| 1904 |
+ out, _, err := runCommandWithOutput(cmd) |
|
| 1905 |
+ if err != nil {
|
|
| 1906 |
+ t.Fatal(err, out) |
|
| 1907 |
+ } |
|
| 1882 | 1908 |
} |
| 1883 | 1909 |
|
| 1884 |
- contID := strings.TrimSpace(out) |
|
| 1910 |
+ // Create a new container and ensure it's not getting the IP or port of some stopped container. |
|
| 1911 |
+ {
|
|
| 1912 |
+ runCmd := exec.Command(dockerBinary, "run", "-d", "-p", "1234", "busybox", "top") |
|
| 1913 |
+ out, _, err := runCommandWithOutput(runCmd) |
|
| 1914 |
+ errorOut(err, t, out) |
|
| 1885 | 1915 |
|
| 1886 |
- f, err := os.Open(filepath.Join("/var/lib/docker/containers", contID, "hosts"))
|
|
| 1887 |
- if err != nil {
|
|
| 1888 |
- t.Fatal(err) |
|
| 1889 |
- } |
|
| 1916 |
+ id := strings.TrimSpace(out) |
|
| 1917 |
+ ip, err := inspectField(id, "NetworkSettings.IPAddress") |
|
| 1918 |
+ errorOut(err, t, out) |
|
| 1890 | 1919 |
|
| 1891 |
- originalContent, err := ioutil.ReadAll(f) |
|
| 1892 |
- f.Close() |
|
| 1920 |
+ portCmd := exec.Command(dockerBinary, "port", id, "1234") |
|
| 1921 |
+ port, _, err := runCommandWithOutput(portCmd) |
|
| 1922 |
+ errorOut(err, t, out) |
|
| 1893 | 1923 |
|
| 1894 |
- if err != nil {
|
|
| 1895 |
- t.Fatal(err) |
|
| 1924 |
+ for i := range ids {
|
|
| 1925 |
+ if ip == ips[i] {
|
|
| 1926 |
+ t.Fatalf("Conflicting IP: %s", ip)
|
|
| 1927 |
+ } |
|
| 1928 |
+ if port == ports[i] {
|
|
| 1929 |
+ t.Fatalf("Conflicting port: %s", port)
|
|
| 1930 |
+ } |
|
| 1931 |
+ } |
|
| 1896 | 1932 |
} |
| 1897 | 1933 |
|
| 1898 |
- out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "restart", "-t", "0", "c1")) |
|
| 1899 |
- if err != nil {
|
|
| 1900 |
- t.Fatal(err, out) |
|
| 1901 |
- } |
|
| 1934 |
+ // Start the containers back, and ensure they are getting the same IPs, MACs and ports. |
|
| 1935 |
+ for i, id := range ids {
|
|
| 1936 |
+ runCmd := exec.Command(dockerBinary, "start", id) |
|
| 1937 |
+ out, _, err := runCommandWithOutput(runCmd) |
|
| 1938 |
+ errorOut(err, t, out) |
|
| 1902 | 1939 |
|
| 1903 |
- f, err = os.Open(filepath.Join("/var/lib/docker/containers", contID, "hosts"))
|
|
| 1904 |
- if err != nil {
|
|
| 1905 |
- t.Fatal(err) |
|
| 1906 |
- } |
|
| 1940 |
+ ip, err := inspectField(id, "NetworkSettings.IPAddress") |
|
| 1941 |
+ errorOut(err, t, out) |
|
| 1907 | 1942 |
|
| 1908 |
- newContent, err := ioutil.ReadAll(f) |
|
| 1909 |
- f.Close() |
|
| 1943 |
+ mac, err := inspectField(id, "NetworkSettings.MacAddress") |
|
| 1944 |
+ errorOut(err, t, out) |
|
| 1910 | 1945 |
|
| 1911 |
- if err != nil {
|
|
| 1912 |
- t.Fatal(err) |
|
| 1913 |
- } |
|
| 1946 |
+ portCmd := exec.Command(dockerBinary, "port", ids[i], "1234") |
|
| 1947 |
+ port, _, err := runCommandWithOutput(portCmd) |
|
| 1948 |
+ errorOut(err, t, out) |
|
| 1914 | 1949 |
|
| 1915 |
- if strings.TrimSpace(string(originalContent)) == strings.TrimSpace(string(newContent)) {
|
|
| 1916 |
- t.Fatalf("expected /etc/hosts to be updated, but wasn't")
|
|
| 1950 |
+ if ips[i] != ip {
|
|
| 1951 |
+ t.Fatalf("Container started with a different IP: %s != %s", ip, ips[i])
|
|
| 1952 |
+ } |
|
| 1953 |
+ if macs[i] != mac {
|
|
| 1954 |
+ t.Fatalf("Container started with a different MAC: %s != %s", mac, macs[i])
|
|
| 1955 |
+ } |
|
| 1956 |
+ if ports[i] != port {
|
|
| 1957 |
+ t.Fatalf("Container started with a different port: %s != %s", port, ports[i])
|
|
| 1958 |
+ } |
|
| 1917 | 1959 |
} |
| 1918 | 1960 |
|
| 1919 | 1961 |
deleteAllContainers() |
| 1920 |
- |
|
| 1921 |
- logDone("run - /etc/hosts updated in parent when restart")
|
|
| 1962 |
+ logDone("run - ips and ports must not change")
|
|
| 1922 | 1963 |
} |
| 1923 | 1964 |
|
| 1924 | 1965 |
// Ensure that CIDFile gets deleted if it's empty |