Merge pull request #1090 from ehazlett/redhat-provisioning

RedHat provisioning
This commit is contained in:
Evan Hazlett 2015-05-26 12:52:29 -04:00
commit 9bf7812473
10 changed files with 374 additions and 9 deletions

View File

@ -1110,6 +1110,8 @@ Options:
The DigitalOcean driver will use `ubuntu-14-04-x64` as the default image.
<<<<<<< HEAD
<<<<<<< HEAD
Environment variables and default values:
| CLI option | Environment variable | Default |
@ -1121,6 +1123,21 @@ Environment variables and default values:
| `--digitalocean-ipv6` | `DIGITALOCEAN_IPV6` | `false` |
| `--digitalocean-private-networking` | `DIGITALOCEAN_PRIVATE_NETWORKING` | `false` |
| `--digitalocean-backups` | `DIGITALOCEAN_BACKUPS` | `false` |
=======
#### exoscale
Create machines on [exoscale](https://www.exoscale.ch/).
Get your API key and API secret key from [API details](https://portal.exoscale.ch/account/api) and pass them to `machine create` with the `--exoscale-api-key` and `--exoscale-api-secret-key` options.
Options:
- `--exoscale-api-key`: Your API key.
- `--exoscale-api-secret-key`: Your API secret key.
- `--exoscale-instance-profile`: Instance profile. Default: `small`.
- `--exoscale-disk-size`: Disk size for the host in GB. Default: `50`.
- `--exoscale-security-group`: Security group. It will be created if it doesn't exist. Default: `docker-machine`.
If a custom security group is provided, you need to ensure that you allow TCP port 2376 in an ingress rule.
#### Generic
Create machines using an existing VM/Host with SSH.
@ -1513,6 +1530,8 @@ Options:
The VMware vSphere driver uses the latest boot2docker image.
<<<<<<< HEAD
<<<<<<< HEAD
Environment variables and default values:
| CLI option | Environment variable | Default |
@ -1563,6 +1582,16 @@ Environment variables and default values:
| `--exoscale-availability-zone` | `EXOSCALE_AVAILABILITY_ZONE` | `ch-gva-2` |
| `--exoscale-keypair` | `EXOSCALE_KEYPAIR` | - |
=======
## Base Operating Systems
The default base operating system for Machine is Boot2Docker on local providers
(VirtualBox, Fusion, Hyper-V, etc) and the latest Ubuntu LTS supported
by the cloud provider. RedHat Enterprise Linux is also supported. To use
RHEL, you will need to select the image accordingly with the provider. For
example, in Amazon EC2, you could use a RedHat 7.1 AMI ("ami-12663b7a") as the
`--amazonec2-ami` option which create an instance using RHEL 7.1 64-bit.
## Release Notes
### Version 0.2.0 (April 16, 2015)

View File

@ -0,0 +1,39 @@
package provision
import (
"github.com/docker/machine/drivers"
)
const (
// TODO: eventually the RPM install process will be integrated
// into the get.docker.com install script; for now
// we install via vendored RPMs
dockerCentosRPMPath = "https://docker-mcn.s3.amazonaws.com/public/redhat/rpms/docker-engine-1.6.1-0.0.20150511.171646.git1b47f9f.el7.centos.x86_64.rpm"
)
func init() {
Register("Centos", &RegisteredProvisioner{
New: NewCentosProvisioner,
})
}
func NewCentosProvisioner(d drivers.Driver) Provisioner {
g := GenericProvisioner{
DockerOptionsDir: "/etc/docker",
DaemonOptionsFile: "/etc/systemd/system/docker.service",
OsReleaseId: "centos",
Packages: []string{},
Driver: d,
}
p := &CentosProvisioner{
RedHatProvisioner{
GenericProvisioner: g,
DockerRPMPath: dockerCentosRPMPath,
},
}
return p
}
type CentosProvisioner struct {
RedHatProvisioner
}

View File

@ -6,7 +6,8 @@ import (
)
type EngineConfigContext struct {
DockerPort int
AuthOptions auth.AuthOptions
EngineOptions engine.EngineOptions
DockerPort int
AuthOptions auth.AuthOptions
EngineOptions engine.EngineOptions
DockerOptionsDir string
}

View File

@ -0,0 +1,39 @@
package provision
import (
"github.com/docker/machine/drivers"
)
const (
// TODO: eventually the RPM install process will be integrated
// into the get.docker.com install script; for now
// we install via vendored RPMs
dockerFedoraRPMPath = "https://docker-mcn.s3.amazonaws.com/public/fedora/rpms/docker-engine-1.6.1-0.0.20150511.171646.git1b47f9f.fc21.x86_64.rpm"
)
func init() {
Register("Fedora", &RegisteredProvisioner{
New: NewFedoraProvisioner,
})
}
func NewFedoraProvisioner(d drivers.Driver) Provisioner {
g := GenericProvisioner{
DockerOptionsDir: "/etc/docker",
DaemonOptionsFile: "/etc/systemd/system/docker.service",
OsReleaseId: "fedora",
Packages: []string{},
Driver: d,
}
p := &FedoraProvisioner{
RedHatProvisioner{
GenericProvisioner: g,
DockerRPMPath: dockerFedoraRPMPath,
},
}
return p
}
type FedoraProvisioner struct {
RedHatProvisioner
}

View File

@ -6,12 +6,18 @@ const (
Restart ServiceAction = iota
Start
Stop
Enable
Disable
DaemonReload
)
var serviceActions = []string{
"restart",
"start",
"stop",
"enable",
"disable",
"daemon-reload",
}
func (s ServiceAction) String() string {

View File

@ -8,6 +8,7 @@ import (
"github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/provision/pkgaction"
"github.com/docker/machine/libmachine/swarm"
"github.com/docker/machine/log"
)
var provisioners = make(map[string]*RegisteredProvisioner)
@ -82,6 +83,7 @@ func DetectProvisioner(d drivers.Driver) (Provisioner, error) {
provisioner.SetOsReleaseInfo(osReleaseInfo)
if provisioner.CompatibleWithHost() {
log.Debugf("Compatible OS: %s", osReleaseInfo.Id)
return provisioner, nil
}
}

View File

@ -0,0 +1,226 @@
package provision
import (
"bytes"
"fmt"
"text/template"
"github.com/docker/machine/drivers"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/provision/pkgaction"
"github.com/docker/machine/libmachine/swarm"
"github.com/docker/machine/log"
"github.com/docker/machine/utils"
)
const (
// TODO: eventually the RPM install process will be integrated
// into the get.docker.com install script; for now
// we install via vendored RPMs
dockerRHELRPMPath = "https://docker-mcn.s3.amazonaws.com/public/redhat/rpms/docker-engine-1.6.1-0.0.20150511.171646.git1b47f9f.el7.centos.x86_64.rpm"
)
func init() {
Register("RedHat", &RegisteredProvisioner{
New: NewRedHatProvisioner,
})
}
func NewRedHatProvisioner(d drivers.Driver) Provisioner {
return &RedHatProvisioner{
GenericProvisioner: GenericProvisioner{
DockerOptionsDir: "/etc/docker",
DaemonOptionsFile: "/etc/systemd/system/docker.service",
OsReleaseId: "rhel",
Packages: []string{
"curl",
},
Driver: d,
},
DockerRPMPath: dockerRHELRPMPath,
}
}
type RedHatProvisioner struct {
GenericProvisioner
DockerRPMPath string
}
func (provisioner *RedHatProvisioner) Service(name string, action pkgaction.ServiceAction) error {
reloadDaemon := false
switch action {
case pkgaction.Start, pkgaction.Restart:
reloadDaemon = true
}
// systemd needs reloaded when config changes on disk; we cannot
// be sure exactly when it changes from the provisioner so
// we call a reload on every restart to be safe
if reloadDaemon {
if _, err := provisioner.SSHCommand("sudo systemctl daemon-reload"); err != nil {
return err
}
}
command := fmt.Sprintf("sudo systemctl %s %s", action.String(), name)
if _, err := provisioner.SSHCommand(command); err != nil {
return err
}
return nil
}
func (provisioner *RedHatProvisioner) Package(name string, action pkgaction.PackageAction) error {
var packageAction string
switch action {
case pkgaction.Install:
packageAction = "install"
case pkgaction.Remove:
packageAction = "remove"
case pkgaction.Upgrade:
packageAction = "upgrade"
}
command := fmt.Sprintf("sudo -E yum %s -y %s", packageAction, name)
if _, err := provisioner.SSHCommand(command); err != nil {
return err
}
return nil
}
func installDocker(provisioner *RedHatProvisioner) error {
if err := provisioner.installOfficialDocker(); err != nil {
return err
}
if err := provisioner.Service("docker", pkgaction.Restart); err != nil {
return err
}
if err := provisioner.Service("docker", pkgaction.Enable); err != nil {
return err
}
return nil
}
func (provisioner *RedHatProvisioner) installOfficialDocker() error {
log.Debug("installing docker")
if _, err := provisioner.SSHCommand(fmt.Sprintf("sudo yum install -y --nogpgcheck %s", provisioner.DockerRPMPath)); err != nil {
return err
}
return nil
}
func (provisioner *RedHatProvisioner) dockerDaemonResponding() bool {
if _, err := provisioner.SSHCommand("sudo docker version"); err != nil {
log.Warn("Error getting SSH command to check if the daemon is up: %s", err)
return false
}
// The daemon is up if the command worked. Carry on.
return true
}
func (provisioner *RedHatProvisioner) Provision(swarmOptions swarm.SwarmOptions, authOptions auth.AuthOptions, engineOptions engine.EngineOptions) error {
provisioner.SwarmOptions = swarmOptions
provisioner.AuthOptions = authOptions
provisioner.EngineOptions = engineOptions
// set default storage driver for redhat
if provisioner.EngineOptions.StorageDriver == "" {
provisioner.EngineOptions.StorageDriver = "devicemapper"
}
if err := provisioner.SetHostname(provisioner.Driver.GetMachineName()); err != nil {
return err
}
for _, pkg := range provisioner.Packages {
log.Debugf("installing base package: name=%s", pkg)
if err := provisioner.Package(pkg, pkgaction.Install); err != nil {
return err
}
}
// update OS -- this is needed for libdevicemapper and the docker install
if _, err := provisioner.SSHCommand("sudo yum -y update"); err != nil {
return err
}
// install docker
if err := installDocker(provisioner); err != nil {
return err
}
if err := utils.WaitFor(provisioner.dockerDaemonResponding); err != nil {
return err
}
if err := makeDockerOptionsDir(provisioner); err != nil {
return err
}
provisioner.AuthOptions = setRemoteAuthOptions(provisioner)
if err := ConfigureAuth(provisioner); err != nil {
return err
}
if err := configureSwarm(provisioner, swarmOptions); err != nil {
return err
}
return nil
}
func (provisioner *RedHatProvisioner) GenerateDockerOptions(dockerPort int) (*DockerOptions, error) {
var (
engineCfg bytes.Buffer
configPath = provisioner.DaemonOptionsFile
)
// remove existing
//if _, err := provisioner.SSHCommand(fmt.Sprintf("sudo rm %s", configPath)); err != nil {
// return nil, err
//}
driverNameLabel := fmt.Sprintf("provider=%s", provisioner.Driver.DriverName())
provisioner.EngineOptions.Labels = append(provisioner.EngineOptions.Labels, driverNameLabel)
// systemd / redhat will not load options if they are on newlines
// instead, it just continues with a different set of options; yeah...
engineConfigTmpl := `[Service]
ExecStart=/usr/bin/docker -d -H tcp://0.0.0.0:{{.DockerPort}} -H unix:///var/run/docker.sock --storage-driver {{.EngineOptions.StorageDriver}} --tlsverify --tlscacert {{.AuthOptions.CaCertRemotePath}} --tlscert {{.AuthOptions.ServerCertRemotePath}} --tlskey {{.AuthOptions.ServerKeyRemotePath}} {{ range .EngineOptions.Labels }}--label {{.}} {{ end }}{{ range .EngineOptions.InsecureRegistry }}--insecure-registry {{.}} {{ end }}{{ range .EngineOptions.RegistryMirror }}--registry-mirror {{.}} {{ end }}{{ range .EngineOptions.ArbitraryFlags }}--{{.}} {{ end }}
MountFlags=slave
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
`
t, err := template.New("engineConfig").Parse(engineConfigTmpl)
if err != nil {
return nil, err
}
engineConfigContext := EngineConfigContext{
DockerPort: dockerPort,
AuthOptions: provisioner.AuthOptions,
EngineOptions: provisioner.EngineOptions,
DockerOptionsDir: provisioner.DockerOptionsDir,
}
t.Execute(&engineCfg, engineConfigContext)
daemonOptsDir := configPath
return &DockerOptions{
EngineOptions: engineCfg.String(),
EngineOptionsPath: daemonOptsDir,
}, nil
}

View File

@ -101,6 +101,7 @@ func ConfigureAuth(p Provisioner) error {
org,
bits,
)
if err != nil {
return fmt.Errorf("error generating server cert: %s", err)
}
@ -185,6 +186,8 @@ func configureSwarm(p Provisioner, swarmOptions swarm.SwarmOptions) error {
return nil
}
log.Debug("configuring swarm")
basePath := p.GetDockerOptionsDir()
ip, err := p.GetDriver().GetIP()
if err != nil {

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/machine/log"
"github.com/docker/machine/utils"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
)
type Client interface {
@ -46,12 +47,14 @@ const (
var (
baseSSHArgs = []string{
"-o", "PasswordAuthentication=no",
"-o", "IdentitiesOnly=yes",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "LogLevel=quiet", // suppress "Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts."
"-o", "ConnectionAttempts=3", // retry 3 times if SSH connection fails
"-o", "ConnectTimeout=10", // timeout after 10 seconds
"-t", // force tty allocation
}
defaultClientType SSHClientType = External
)
@ -156,7 +159,29 @@ func (client NativeClient) Output(command string) (string, error) {
output, err := session.CombinedOutput(command)
return string(output), err
fd := int(os.Stdin.Fd())
if err != nil {
return string(output), err
}
termWidth, termHeight, err := terminal.GetSize(fd)
if err != nil {
return string(output), err
}
modes := ssh.TerminalModes{
ssh.ECHO: 1,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
// request tty -- fixes error with hosts that use
// "Defaults requiretty" in /etc/sudoers - I'm looking at you RedHat
if err := session.RequestPty("xterm-256color", termHeight, termWidth, modes); err != nil {
return string(output), err
}
return string(output), session.Run(command)
}
func (client NativeClient) Shell() error {

View File

@ -121,7 +121,6 @@ func GenerateCert(hosts []string, certFile, keyFile, caFile, caKeyFile, org stri
for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
@ -131,13 +130,11 @@ func GenerateCert(hosts []string, certFile, keyFile, caFile, caKeyFile, org stri
tlsCert, err := tls.LoadX509KeyPair(caFile, caKeyFile)
if err != nil {
return err
}
priv, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return err
}
x509Cert, err := x509.ParseCertificate(tlsCert.Certificate[0])
@ -153,7 +150,6 @@ func GenerateCert(hosts []string, certFile, keyFile, caFile, caKeyFile, org stri
certOut, err := os.Create(certFile)
if err != nil {
return err
}
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
@ -162,7 +158,6 @@ func GenerateCert(hosts []string, certFile, keyFile, caFile, caKeyFile, org stri
keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})