1
0
mirror of https://github.com/docker/docs.git synced 2026-11-05 00:14:17 +03:00

Merge pull request #1685 from nathanleclaire/daemon_wait_over_ssh

Fix Docker daemon wait
This commit is contained in:
Evan Hazlett
2015-09-24 16:56:37 -04:00
11 changed files with 151 additions and 89 deletions

View File

@@ -136,7 +136,7 @@ func GetCreateFlags() []cli.Flag {
}
func (d *Driver) GetSSHHostname() (string, error) {
return "localhost", nil
return "127.0.0.1", nil
}
func (d *Driver) GetSSHUsername() string {

View File

@@ -2,17 +2,12 @@ package drivers
import (
"fmt"
"strings"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/mcnutils"
"github.com/docker/machine/libmachine/ssh"
)
const (
ErrExitCode255 = "255"
)
func GetSSHClientFromDriver(d Driver) (ssh.Client, error) {
addr, err := d.GetSSHHostname()
if err != nil {
@@ -33,10 +28,6 @@ func GetSSHClientFromDriver(d Driver) (ssh.Client, error) {
}
func isErr255Exit(err error) bool {
return strings.Contains(err.Error(), ErrExitCode255)
}
func RunSSHCommandFromDriver(d Driver, command string) (string, error) {
client, err := GetSSHClientFromDriver(d)
if err != nil {
@@ -47,34 +38,21 @@ func RunSSHCommandFromDriver(d Driver, command string) (string, error) {
output, err := client.Output(command)
log.Debugf("SSH cmd err, output: %v: %s", err, output)
if err != nil && !isErr255Exit(err) {
log.Error("SSH cmd error!")
log.Errorf("command: %s", command)
log.Errorf("err : %v", err)
log.Fatalf("output : %s", output)
if err != nil {
returnedErr := fmt.Errorf(`Something went wrong running an SSH command!
command : %s
err : %v
output : %s
`, command, err, output)
return "", returnedErr
}
return output, err
return output, nil
}
func sshAvailableFunc(d Driver) func() bool {
return func() bool {
log.Debug("Getting to WaitForSSH function...")
hostname, err := d.GetSSHHostname()
if err != nil {
log.Debugf("Error getting IP address waiting for SSH: %s", err)
return false
}
port, err := d.GetSSHPort()
if err != nil {
log.Debugf("Error getting SSH port: %s", err)
return false
}
if err := ssh.WaitForTCP(fmt.Sprintf("%s:%d", hostname, port)); err != nil {
log.Debugf("Error waiting for TCP waiting for SSH: %s", err)
return false
}
if _, err := RunSSHCommandFromDriver(d, "exit 0"); err != nil {
log.Debugf("Error getting ssh command 'exit 0' : %s", err)
return false
@@ -85,7 +63,7 @@ func sshAvailableFunc(d Driver) func() bool {
func WaitForSSH(d Driver) error {
if err := mcnutils.WaitFor(sshAvailableFunc(d)); err != nil {
return fmt.Errorf("Too many retries. Last error: %s", err)
return fmt.Errorf("Too many retries waiting for SSH to be available. Last error: %s", err)
}
return nil
}

View File

@@ -175,7 +175,3 @@ func (h *Host) ConfigureAuth() error {
return nil
}
func WaitForSSH(h *Host) error {
return drivers.WaitForSSH(h.Driver)
}

View File

@@ -59,7 +59,7 @@ func Create(store persist.Store, h *host.Host) error {
}
log.Info("Machine is running, waiting for SSH to be available...")
if err := host.WaitForSSH(h); err != nil {
if err := drivers.WaitForSSH(h.Driver); err != nil {
return fmt.Errorf("Error waiting for SSH: %s", err)
}

View File

@@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"io"
"net"
"os"
"runtime"
"strconv"
@@ -95,18 +94,6 @@ func WaitFor(f func() bool) error {
return WaitForSpecific(f, 60, 3*time.Second)
}
func WaitForDocker(ip string, daemonPort int) error {
return WaitFor(func() bool {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", ip, daemonPort))
if err != nil {
log.Debugf("Daemon not responding yet: %s", err)
return false
}
conn.Close()
return true
})
}
func DumpVal(vals ...interface{}) {
for _, val := range vals {
prettyJSON, err := json.MarshalIndent(val, "", " ")

View File

@@ -3,8 +3,10 @@ package provision
import (
"bytes"
"fmt"
"net"
"path"
"text/template"
"time"
"github.com/docker/machine/commands/mcndirs"
"github.com/docker/machine/libmachine/auth"
@@ -182,6 +184,35 @@ func (provisioner *Boot2DockerProvisioner) GetOsReleaseInfo() (*OsRelease, error
}
func (provisioner *Boot2DockerProvisioner) Provision(swarmOptions swarm.SwarmOptions, authOptions auth.AuthOptions, engineOptions engine.EngineOptions) error {
const (
dockerPort = 2376
)
defer func() {
ip, err := provisioner.Driver.GetIP()
if err != nil {
log.Fatalf("Could not get IP address for created machine: %s", err)
}
if conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, dockerPort), 5*time.Second); err != nil {
log.Warn(`
This machine has been allocated an IP address, but Docker Machine could not
reach it successfully.
SSH for the machine should still work, but connecting to exposed ports, such as
the Docker daemon port (usually <ip>:2376), may not work properly.
You may need to add the route manually, or use another related workaround.
This could be due to a VPN, proxy, or host file configuration issue.
You also might want to clear any VirtualBox host only interfaces you are not using.`)
log.Fatal(err)
} else {
conn.Close()
}
}()
provisioner.SwarmOptions = swarmOptions
provisioner.AuthOptions = authOptions
provisioner.EngineOptions = engineOptions
@@ -194,14 +225,9 @@ func (provisioner *Boot2DockerProvisioner) Provision(swarmOptions swarm.SwarmOpt
return err
}
ip, err := provisioner.GetDriver().GetIP()
if err != nil {
return err
}
// b2d hosts need to wait for the daemon to be up
// before continuing with provisioning
if err := mcnutils.WaitForDocker(ip, 2376); err != nil {
if err := waitForDocker(provisioner, dockerPort); err != nil {
return err
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/docker/machine/libmachine/drivers"
"github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/mcnutils"
"github.com/docker/machine/libmachine/provision/pkgaction"
"github.com/docker/machine/libmachine/provision/serviceaction"
"github.com/docker/machine/libmachine/swarm"
@@ -59,11 +58,7 @@ func (provisioner *CoreOSProvisioner) Service(name string, action serviceaction.
// wait until docker is running
if (name == "docker") && (action.String() == "start") {
ip, err := provisioner.GetDriver().GetIP()
if err != nil {
return err
}
if err := mcnutils.WaitForDocker(ip, 2376); err != nil {
if err := waitForDocker(provisioner, 2376); err != nil {
return err
}
}

View File

@@ -2,6 +2,7 @@ package provision
import (
"errors"
"fmt"
)
var (
@@ -9,3 +10,17 @@ var (
ErrSSHCommandFailed = errors.New("SSH command failure")
ErrNotImplemented = errors.New("Runtime not implemented")
)
type ErrDaemonAvailable struct {
wrappedErr error
}
func (e ErrDaemonAvailable) Error() string {
return fmt.Sprintf("Unable to verify the Docker daemon is listening: %s", e.wrappedErr)
}
func NewErrDaemonAvailable(err error) ErrDaemonAvailable {
return ErrDaemonAvailable{
wrappedErr: err,
}
}

View File

@@ -6,8 +6,10 @@ import (
"net/url"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/cert"
@@ -58,12 +60,13 @@ func ConfigureAuth(p Provisioner) error {
err error
)
machineName := p.GetDriver().GetMachineName()
driver := p.GetDriver()
machineName := driver.GetMachineName()
authOptions := p.GetAuthOptions()
org := machineName
bits := 2048
ip, err := p.GetDriver().GetIP()
ip, err := driver.GetIP()
if err != nil {
return err
}
@@ -143,7 +146,7 @@ func ConfigureAuth(p Provisioner) error {
return err
}
dockerUrl, err := p.GetDriver().GetURL()
dockerUrl, err := driver.GetURL()
if err != nil {
return err
}
@@ -176,10 +179,50 @@ func ConfigureAuth(p Provisioner) error {
return err
}
// TODO: Do not hardcode daemon port, ask the driver
if err := mcnutils.WaitForDocker(ip, dockerPort); err != nil {
if err := waitForDocker(p, dockerPort); err != nil {
return err
}
return nil
}
func matchNetstatOut(reDaemonListening, netstatOut string) bool {
// TODO: I would really prefer this be a Scanner directly on
// the STDOUT of the executed command than to do all the string
// manipulation hokey-pokey.
//
// TODO: Unit test this matching.
for _, line := range strings.Split(netstatOut, "\n") {
match, err := regexp.MatchString(reDaemonListening, line)
if err != nil {
log.Warnf("Regex warning: %s", err)
}
if match && line != "" {
return true
}
}
return false
}
func checkDaemonUp(p Provisioner, dockerPort int) func() bool {
reDaemonListening := fmt.Sprintf(":%d.*LISTEN", dockerPort)
return func() bool {
// HACK: Check netstat's output to see if anyone's listening on the Docker API port.
netstatOut, err := p.SSHCommand("netstat -a")
if err != nil {
log.Warnf("Error running SSH command: %s", err)
return false
}
return matchNetstatOut(reDaemonListening, netstatOut)
}
}
func waitForDocker(p Provisioner, dockerPort int) error {
if err := mcnutils.WaitForSpecific(checkDaemonUp(p, dockerPort), 5, 3*time.Second); err != nil {
return NewErrDaemonAvailable(err)
}
return nil
}

View File

@@ -10,6 +10,50 @@ import (
"github.com/docker/machine/libmachine/auth"
)
var (
reDaemonListening = ":2376.*LISTEN"
)
func TestMatchNetstatOutMissing(t *testing.T) {
nsOut := `Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN
tcp 0 72 192.168.25.141:ssh 192.168.25.1:63213 ESTABLISHED
tcp 0 0 :::ssh :::* LISTEN
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags Type State I-Node Path
unix 2 [ ACC ] STREAM LISTENING 17990 /var/run/acpid.socket
unix 2 [ ACC ] SEQPACKET LISTENING 14233 /run/udev/control
unix 3 [ ] STREAM CONNECTED 18688
unix 3 [ ] DGRAM 14243
unix 3 [ ] STREAM CONNECTED 18689
unix 3 [ ] DGRAM 14242`
if matchNetstatOut(reDaemonListening, nsOut) {
t.Fatal("Expected not to match the netstat output as showing the daemon listening but got a match")
}
}
func TestMatchNetstatOutPresent(t *testing.T) {
nsOut := `Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN
tcp 0 72 192.168.25.141:ssh 192.168.25.1:63235 ESTABLISHED
tcp 0 0 :::2376 :::* LISTEN
tcp 0 0 :::ssh :::* LISTEN
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags Type State I-Node Path
unix 2 [ ACC ] STREAM LISTENING 17990 /var/run/acpid.socket
unix 2 [ ACC ] SEQPACKET LISTENING 14233 /run/udev/control
unix 2 [ ACC ] STREAM LISTENING 19365 /var/run/docker.sock
unix 3 [ ] STREAM CONNECTED 19774
unix 3 [ ] STREAM CONNECTED 19775
unix 3 [ ] DGRAM 14243
unix 3 [ ] DGRAM 14242`
if !matchNetstatOut(reDaemonListening, nsOut) {
t.Fatal("Expected to match the netstat output as showing the daemon listening but didn't")
}
}
func TestGenerateDockerOptionsBoot2Docker(t *testing.T) {
p := &Boot2DockerProvisioner{
Driver: &fakedriver.FakeDriver{},

View File

@@ -1,22 +0,0 @@
package ssh
import (
"net"
"time"
"github.com/docker/machine/libmachine/log"
)
func WaitForTCP(addr string) error {
for {
log.Debugf("Testing TCP connection to: %s", addr)
conn, err := net.DialTimeout("tcp", addr, 2*time.Second)
if err != nil {
continue
}
defer conn.Close()
return nil
}
}