Update /etc/hosts when linked container is restarted
| ... | ... |
@@ -297,6 +297,9 @@ func (container *Container) Start() (err error) {
|
| 297 | 297 |
if err := container.initializeNetworking(); err != nil {
|
| 298 | 298 |
return err |
| 299 | 299 |
} |
| 300 |
+ if err := container.updateParentsHosts(); err != nil {
|
|
| 301 |
+ return err |
|
| 302 |
+ } |
|
| 300 | 303 |
container.verifyDaemonSettings() |
| 301 | 304 |
if err := prepareVolumesForContainer(container); err != nil {
|
| 302 | 305 |
return err |
| ... | ... |
@@ -390,10 +393,7 @@ func (container *Container) buildHostnameFile() error {
|
| 390 | 390 |
return ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644) |
| 391 | 391 |
} |
| 392 | 392 |
|
| 393 |
-func (container *Container) buildHostnameAndHostsFiles(IP string) error {
|
|
| 394 |
- if err := container.buildHostnameFile(); err != nil {
|
|
| 395 |
- return err |
|
| 396 |
- } |
|
| 393 |
+func (container *Container) buildHostsFiles(IP string) error {
|
|
| 397 | 394 |
|
| 398 | 395 |
hostsPath, err := container.getRootResourcePath("hosts")
|
| 399 | 396 |
if err != nil {
|
| ... | ... |
@@ -416,6 +416,14 @@ func (container *Container) buildHostnameAndHostsFiles(IP string) error {
|
| 416 | 416 |
return etchosts.Build(container.HostsPath, IP, container.Config.Hostname, container.Config.Domainname, &extraContent) |
| 417 | 417 |
} |
| 418 | 418 |
|
| 419 |
+func (container *Container) buildHostnameAndHostsFiles(IP string) error {
|
|
| 420 |
+ if err := container.buildHostnameFile(); err != nil {
|
|
| 421 |
+ return err |
|
| 422 |
+ } |
|
| 423 |
+ |
|
| 424 |
+ return container.buildHostsFiles(IP) |
|
| 425 |
+} |
|
| 426 |
+ |
|
| 419 | 427 |
func (container *Container) allocateNetwork() error {
|
| 420 | 428 |
mode := container.hostConfig.NetworkMode |
| 421 | 429 |
if container.Config.NetworkDisabled || mode.IsContainer() || mode.IsHost() {
|
| ... | ... |
@@ -878,6 +886,26 @@ func (container *Container) setupContainerDns() error {
|
| 878 | 878 |
return ioutil.WriteFile(container.ResolvConfPath, resolvConf, 0644) |
| 879 | 879 |
} |
| 880 | 880 |
|
| 881 |
+func (container *Container) updateParentsHosts() error {
|
|
| 882 |
+ parents, err := container.daemon.Parents(container.Name) |
|
| 883 |
+ if err != nil {
|
|
| 884 |
+ return err |
|
| 885 |
+ } |
|
| 886 |
+ for _, cid := range parents {
|
|
| 887 |
+ if cid == "0" {
|
|
| 888 |
+ continue |
|
| 889 |
+ } |
|
| 890 |
+ |
|
| 891 |
+ c := container.daemon.Get(cid) |
|
| 892 |
+ if c != nil && !container.daemon.config.DisableNetwork && !container.hostConfig.NetworkMode.IsContainer() && !container.hostConfig.NetworkMode.IsHost() {
|
|
| 893 |
+ if err := etchosts.Update(c.HostsPath, container.NetworkSettings.IPAddress, container.Name[1:]); err != nil {
|
|
| 894 |
+ return fmt.Errorf("Failed to update /etc/hosts in parent container: %v", err)
|
|
| 895 |
+ } |
|
| 896 |
+ } |
|
| 897 |
+ } |
|
| 898 |
+ return nil |
|
| 899 |
+} |
|
| 900 |
+ |
|
| 881 | 901 |
func (container *Container) initializeNetworking() error {
|
| 882 | 902 |
var err error |
| 883 | 903 |
if container.hostConfig.NetworkMode.IsHost() {
|
| ... | ... |
@@ -621,6 +621,15 @@ func (daemon *Daemon) Children(name string) (map[string]*Container, error) {
|
| 621 | 621 |
return children, nil |
| 622 | 622 |
} |
| 623 | 623 |
|
| 624 |
+func (daemon *Daemon) Parents(name string) ([]string, error) {
|
|
| 625 |
+ name, err := GetFullContainerName(name) |
|
| 626 |
+ if err != nil {
|
|
| 627 |
+ return nil, err |
|
| 628 |
+ } |
|
| 629 |
+ |
|
| 630 |
+ return daemon.containerGraph.Parents(name) |
|
| 631 |
+} |
|
| 632 |
+ |
|
| 624 | 633 |
func (daemon *Daemon) RegisterLink(parent, child *Container, alias string) error {
|
| 625 | 634 |
fullName := path.Join(parent.Name, alias) |
| 626 | 635 |
if !daemon.containerGraph.Exists(fullName) {
|
| ... | ... |
@@ -150,7 +150,10 @@ Four different options affect container domain name services. |
| 150 | 150 |
`CONTAINER_NAME`. This lets processes inside the new container |
| 151 | 151 |
connect to the hostname `ALIAS` without having to know its IP. The |
| 152 | 152 |
`--link=` option is discussed in more detail below, in the section |
| 153 |
- [Communication between containers](#between-containers). |
|
| 153 |
+ [Communication between containers](#between-containers). Because |
|
| 154 |
+ Docker may assign a different IP address to the linked containers |
|
| 155 |
+ on restart, Docker updates the ALIAS entry in the /etc/hosts file |
|
| 156 |
+ of the recipient containers. |
|
| 154 | 157 |
|
| 155 | 158 |
* `--dns=IP_ADDRESS...` — sets the IP addresses added as `server` |
| 156 | 159 |
lines to the container's `/etc/resolv.conf` file. Processes in the |
| ... | ... |
@@ -432,6 +432,9 @@ mechanism to communicate with a linked container by its alias: |
| 432 | 432 |
$ docker run -d --name servicename busybox sleep 30 |
| 433 | 433 |
$ docker run -i -t --link servicename:servicealias busybox ping -c 1 servicealias |
| 434 | 434 |
|
| 435 |
+If you restart the source container (`servicename` in this case), the recipient |
|
| 436 |
+container's `/etc/hosts` entry will be automatically updated. |
|
| 437 |
+ |
|
| 435 | 438 |
## VOLUME (Shared Filesystems) |
| 436 | 439 |
|
| 437 | 440 |
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. |
| ... | ... |
@@ -241,6 +241,16 @@ to make use of your `db` container. |
| 241 | 241 |
> example, you could have multiple (differently named) web containers attached to your |
| 242 | 242 |
>`db` container. |
| 243 | 243 |
|
| 244 |
+If you restart the source container, the linked containers `/etc/hosts` files |
|
| 245 |
+will be automatically updated with the source container's new IP address, |
|
| 246 |
+allowing linked communication to continue. |
|
| 247 |
+ |
|
| 248 |
+ $ sudo docker restart db |
|
| 249 |
+ root@aed84ee21bde:/opt/webapp# cat /etc/hosts |
|
| 250 |
+ 172.17.0.7 aed84ee21bde |
|
| 251 |
+ . . . |
|
| 252 |
+ 172.17.0.9 db |
|
| 253 |
+ |
|
| 244 | 254 |
# Next step |
| 245 | 255 |
|
| 246 | 256 |
Now that you know how to link Docker containers together, the next step is |
| ... | ... |
@@ -1723,3 +1723,50 @@ func TestBindMounts(t *testing.T) {
|
| 1723 | 1723 |
t.Fatalf("Output should be %q, actual out: %q", expected, content)
|
| 1724 | 1724 |
} |
| 1725 | 1725 |
} |
| 1726 |
+ |
|
| 1727 |
+func TestHostsLinkedContainerUpdate(t *testing.T) {
|
|
| 1728 |
+ tmpdir, err := ioutil.TempDir("", "docker-integration")
|
|
| 1729 |
+ if err != nil {
|
|
| 1730 |
+ t.Fatal(err) |
|
| 1731 |
+ } |
|
| 1732 |
+ defer os.RemoveAll(tmpdir) |
|
| 1733 |
+ |
|
| 1734 |
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "c1", "busybox", "sleep", "5")) |
|
| 1735 |
+ if err != nil {
|
|
| 1736 |
+ t.Fatal(err, out) |
|
| 1737 |
+ } |
|
| 1738 |
+ |
|
| 1739 |
+ // TODO fix docker cp and /etc/hosts |
|
| 1740 |
+ out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--link", "c1:c1", "--name", "c2", "busybox", "sh", "-c", "while true;do cp /etc/hosts /hosts; done")) |
|
| 1741 |
+ if err != nil {
|
|
| 1742 |
+ t.Fatal(err, out) |
|
| 1743 |
+ } |
|
| 1744 |
+ |
|
| 1745 |
+ out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "cp", "c2:/hosts", tmpdir+"/1")) |
|
| 1746 |
+ if err != nil {
|
|
| 1747 |
+ t.Fatal(err, out) |
|
| 1748 |
+ } |
|
| 1749 |
+ |
|
| 1750 |
+ out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "restart", "-t", "0", "c1")) |
|
| 1751 |
+ if err != nil {
|
|
| 1752 |
+ t.Fatal(err, out) |
|
| 1753 |
+ } |
|
| 1754 |
+ |
|
| 1755 |
+ out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "cp", "c2:/hosts", tmpdir+"/2")) |
|
| 1756 |
+ if err != nil {
|
|
| 1757 |
+ t.Fatal(err, out) |
|
| 1758 |
+ } |
|
| 1759 |
+ |
|
| 1760 |
+ out, _, _, err = runCommandWithStdoutStderr(exec.Command("diff", tmpdir+"/1", tmpdir+"/2"))
|
|
| 1761 |
+ if err == nil {
|
|
| 1762 |
+ t.Fatalf("Expecting error, got none")
|
|
| 1763 |
+ } |
|
| 1764 |
+ out = stripTrailingCharacters(out) |
|
| 1765 |
+ if out == "" {
|
|
| 1766 |
+ t.Fatalf("expected /etc/hosts to be updated, but wasn't")
|
|
| 1767 |
+ } |
|
| 1768 |
+ |
|
| 1769 |
+ deleteAllContainers() |
|
| 1770 |
+ |
|
| 1771 |
+ logDone("run - /etc/hosts updated in parent when restart")
|
|
| 1772 |
+} |
| ... | ... |
@@ -281,6 +281,18 @@ func (db *Database) Children(name string, depth int) ([]WalkMeta, error) {
|
| 281 | 281 |
return db.children(e, name, depth, nil) |
| 282 | 282 |
} |
| 283 | 283 |
|
| 284 |
+// Return the parents of a specified entity |
|
| 285 |
+func (db *Database) Parents(name string) ([]string, error) {
|
|
| 286 |
+ db.mux.RLock() |
|
| 287 |
+ defer db.mux.RUnlock() |
|
| 288 |
+ |
|
| 289 |
+ e, err := db.get(name) |
|
| 290 |
+ if err != nil {
|
|
| 291 |
+ return nil, err |
|
| 292 |
+ } |
|
| 293 |
+ return db.parents(e) |
|
| 294 |
+} |
|
| 295 |
+ |
|
| 284 | 296 |
// Return the refrence count for a specified id |
| 285 | 297 |
func (db *Database) Refs(id string) int {
|
| 286 | 298 |
db.mux.RLock() |
| ... | ... |
@@ -466,6 +478,28 @@ func (db *Database) children(e *Entity, name string, depth int, entities []WalkM |
| 466 | 466 |
return entities, nil |
| 467 | 467 |
} |
| 468 | 468 |
|
| 469 |
+func (db *Database) parents(e *Entity) (parents []string, err error) {
|
|
| 470 |
+ if e == nil {
|
|
| 471 |
+ return parents, nil |
|
| 472 |
+ } |
|
| 473 |
+ |
|
| 474 |
+ rows, err := db.conn.Query("SELECT parent_id FROM edge where entity_id = ?;", e.id)
|
|
| 475 |
+ if err != nil {
|
|
| 476 |
+ return nil, err |
|
| 477 |
+ } |
|
| 478 |
+ defer rows.Close() |
|
| 479 |
+ |
|
| 480 |
+ for rows.Next() {
|
|
| 481 |
+ var parentId string |
|
| 482 |
+ if err := rows.Scan(&parentId); err != nil {
|
|
| 483 |
+ return nil, err |
|
| 484 |
+ } |
|
| 485 |
+ parents = append(parents, parentId) |
|
| 486 |
+ } |
|
| 487 |
+ |
|
| 488 |
+ return parents, nil |
|
| 489 |
+} |
|
| 490 |
+ |
|
| 469 | 491 |
// Return the entity based on the parent path and name |
| 470 | 492 |
func (db *Database) child(parent *Entity, name string) *Entity {
|
| 471 | 493 |
var id string |
| ... | ... |
@@ -34,7 +34,7 @@ func TestNewDatabase(t *testing.T) {
|
| 34 | 34 |
defer destroyTestDb(dbpath) |
| 35 | 35 |
} |
| 36 | 36 |
|
| 37 |
-func TestCreateRootEnity(t *testing.T) {
|
|
| 37 |
+func TestCreateRootEntity(t *testing.T) {
|
|
| 38 | 38 |
db, dbpath := newTestDb(t) |
| 39 | 39 |
defer destroyTestDb(dbpath) |
| 40 | 40 |
root := db.RootEntity() |
| ... | ... |
@@ -94,6 +94,84 @@ func TestCreateChild(t *testing.T) {
|
| 94 | 94 |
} |
| 95 | 95 |
} |
| 96 | 96 |
|
| 97 |
+func TestParents(t *testing.T) {
|
|
| 98 |
+ db, dbpath := newTestDb(t) |
|
| 99 |
+ defer destroyTestDb(dbpath) |
|
| 100 |
+ |
|
| 101 |
+ for i := 1; i < 6; i++ {
|
|
| 102 |
+ a := strconv.Itoa(i) |
|
| 103 |
+ if _, err := db.Set("/"+a, a); err != nil {
|
|
| 104 |
+ t.Fatal(err) |
|
| 105 |
+ } |
|
| 106 |
+ } |
|
| 107 |
+ |
|
| 108 |
+ for i := 6; i < 11; i++ {
|
|
| 109 |
+ a := strconv.Itoa(i) |
|
| 110 |
+ p := strconv.Itoa(i - 5) |
|
| 111 |
+ |
|
| 112 |
+ key := fmt.Sprintf("/%s/%s", p, a)
|
|
| 113 |
+ |
|
| 114 |
+ if _, err := db.Set(key, a); err != nil {
|
|
| 115 |
+ t.Fatal(err) |
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ parents, err := db.Parents(key) |
|
| 119 |
+ if err != nil {
|
|
| 120 |
+ t.Fatal(err) |
|
| 121 |
+ } |
|
| 122 |
+ |
|
| 123 |
+ if len(parents) != 1 {
|
|
| 124 |
+ t.Fatalf("Expected 2 entries for %s got %d", key, len(parents))
|
|
| 125 |
+ } |
|
| 126 |
+ |
|
| 127 |
+ if parents[0] != p {
|
|
| 128 |
+ t.Fatalf("ID %s received, %s expected", parents[0], p)
|
|
| 129 |
+ } |
|
| 130 |
+ } |
|
| 131 |
+} |
|
| 132 |
+ |
|
| 133 |
+func TestChildren(t *testing.T) {
|
|
| 134 |
+ db, dbpath := newTestDb(t) |
|
| 135 |
+ defer destroyTestDb(dbpath) |
|
| 136 |
+ |
|
| 137 |
+ str := "/" |
|
| 138 |
+ for i := 1; i < 6; i++ {
|
|
| 139 |
+ a := strconv.Itoa(i) |
|
| 140 |
+ if _, err := db.Set(str+a, a); err != nil {
|
|
| 141 |
+ t.Fatal(err) |
|
| 142 |
+ } |
|
| 143 |
+ |
|
| 144 |
+ str = str + a + "/" |
|
| 145 |
+ } |
|
| 146 |
+ |
|
| 147 |
+ str = "/" |
|
| 148 |
+ for i := 10; i < 30; i++ { // 20 entities
|
|
| 149 |
+ a := strconv.Itoa(i) |
|
| 150 |
+ if _, err := db.Set(str+a, a); err != nil {
|
|
| 151 |
+ t.Fatal(err) |
|
| 152 |
+ } |
|
| 153 |
+ |
|
| 154 |
+ str = str + a + "/" |
|
| 155 |
+ } |
|
| 156 |
+ entries, err := db.Children("/", 5)
|
|
| 157 |
+ if err != nil {
|
|
| 158 |
+ t.Fatal(err) |
|
| 159 |
+ } |
|
| 160 |
+ |
|
| 161 |
+ if len(entries) != 11 {
|
|
| 162 |
+ t.Fatalf("Expect 11 entries for / got %d", len(entries))
|
|
| 163 |
+ } |
|
| 164 |
+ |
|
| 165 |
+ entries, err = db.Children("/", 20)
|
|
| 166 |
+ if err != nil {
|
|
| 167 |
+ t.Fatal(err) |
|
| 168 |
+ } |
|
| 169 |
+ |
|
| 170 |
+ if len(entries) != 25 {
|
|
| 171 |
+ t.Fatalf("Expect 25 entries for / got %d", len(entries))
|
|
| 172 |
+ } |
|
| 173 |
+} |
|
| 174 |
+ |
|
| 97 | 175 |
func TestListAllRootChildren(t *testing.T) {
|
| 98 | 176 |
db, dbpath := newTestDb(t) |
| 99 | 177 |
defer destroyTestDb(dbpath) |
| ... | ... |
@@ -4,6 +4,7 @@ import ( |
| 4 | 4 |
"bytes" |
| 5 | 5 |
"fmt" |
| 6 | 6 |
"io/ioutil" |
| 7 |
+ "regexp" |
|
| 7 | 8 |
) |
| 8 | 9 |
|
| 9 | 10 |
var defaultContent = map[string]string{
|
| ... | ... |
@@ -41,3 +42,12 @@ func Build(path, IP, hostname, domainname string, extraContent *map[string]strin |
| 41 | 41 |
|
| 42 | 42 |
return ioutil.WriteFile(path, content.Bytes(), 0644) |
| 43 | 43 |
} |
| 44 |
+ |
|
| 45 |
+func Update(path, IP, hostname string) error {
|
|
| 46 |
+ old, err := ioutil.ReadFile(path) |
|
| 47 |
+ if err != nil {
|
|
| 48 |
+ return err |
|
| 49 |
+ } |
|
| 50 |
+ var re = regexp.MustCompile(fmt.Sprintf("(\\S*)(\\t%s)", regexp.QuoteMeta(hostname)))
|
|
| 51 |
+ return ioutil.WriteFile(path, re.ReplaceAll(old, []byte(IP+"$2")), 0644) |
|
| 52 |
+} |
| ... | ... |
@@ -72,3 +72,37 @@ func TestBuildNoIP(t *testing.T) {
|
| 72 | 72 |
t.Fatalf("Expected to find '%s' got '%s'", expected, content)
|
| 73 | 73 |
} |
| 74 | 74 |
} |
| 75 |
+ |
|
| 76 |
+func TestUpdate(t *testing.T) {
|
|
| 77 |
+ file, err := ioutil.TempFile("", "")
|
|
| 78 |
+ if err != nil {
|
|
| 79 |
+ t.Fatal(err) |
|
| 80 |
+ } |
|
| 81 |
+ defer os.Remove(file.Name()) |
|
| 82 |
+ |
|
| 83 |
+ if err := Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil); err != nil {
|
|
| 84 |
+ t.Fatal(err) |
|
| 85 |
+ } |
|
| 86 |
+ |
|
| 87 |
+ content, err := ioutil.ReadFile(file.Name()) |
|
| 88 |
+ if err != nil {
|
|
| 89 |
+ t.Fatal(err) |
|
| 90 |
+ } |
|
| 91 |
+ |
|
| 92 |
+ if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
|
|
| 93 |
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
|
|
| 94 |
+ } |
|
| 95 |
+ |
|
| 96 |
+ if err := Update(file.Name(), "1.1.1.1", "testhostname"); err != nil {
|
|
| 97 |
+ t.Fatal(err) |
|
| 98 |
+ } |
|
| 99 |
+ |
|
| 100 |
+ content, err = ioutil.ReadFile(file.Name()) |
|
| 101 |
+ if err != nil {
|
|
| 102 |
+ t.Fatal(err) |
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ if expected := "1.1.1.1\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
|
|
| 106 |
+ t.Fatalf("Expected to find '%s' got '%s'", expected, content)
|
|
| 107 |
+ } |
|
| 108 |
+} |