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:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -175,7 +175,3 @@ func (h *Host) ConfigureAuth() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WaitForSSH(h *Host) error {
|
||||
return drivers.WaitForSSH(h.Driver)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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, "", " ")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user