From 2faa68426f684537b7a239295e26236abbbe0331 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 24 Jul 2018 21:56:46 -0400 Subject: [PATCH] Docker installation from tar.gz Ubuntu 18.04 doesn't have a package for docker 17.03, but we can still support it by using the tar.gz package. This could be a nice fallback for other operating systems in future, and it might prove to be more reliable than the OS packages. But start with supporting ubuntu 18.04 with older docker versions! --- nodeup/pkg/model/docker.go | 77 +++++++++++-- upup/pkg/fi/nodeup/nodetasks/BUILD.bazel | 1 + upup/pkg/fi/nodeup/nodetasks/archive.go | 8 ++ upup/pkg/fi/nodeup/nodetasks/group.go | 135 +++++++++++++++++++++++ upup/pkg/fi/nodeup/nodetasks/service.go | 2 +- 5 files changed, 214 insertions(+), 9 deletions(-) create mode 100644 upup/pkg/fi/nodeup/nodetasks/group.go diff --git a/nodeup/pkg/model/docker.go b/nodeup/pkg/model/docker.go index dc4aa5f4b2..5ae18b02c6 100644 --- a/nodeup/pkg/model/docker.go +++ b/nodeup/pkg/model/docker.go @@ -52,6 +52,9 @@ type dockerVersion struct { Distros []distros.Distribution Dependencies []string Architectures []Architecture + + // PlainBinary indicates that the Source is not an OS, but a "bare" tar.gz + PlainBinary bool } // DefaultDockerVersion is the (legacy) docker version we use if one is not specified in the manifest. @@ -414,6 +417,17 @@ var dockerVersions = []dockerVersion{ Dependencies: []string{"bridge-utils", "iptables", "libapparmor1", "libltdl7", "perl"}, }, + // 17.03.2 - Ubuntu Bionic via binary download (no packages available) + { + DockerVersion: "17.03.2", + PlainBinary: true, + Distros: []distros.Distribution{distros.DistributionBionic}, + Architectures: []Architecture{ArchitectureAmd64}, + Source: "http://download.docker.com/linux/static/stable/x86_64/docker-17.03.2-ce.tgz", + Hash: "141716ae046016a1792ce232a0f4c8eed7fe37d1", + Dependencies: []string{"bridge-utils", "iptables", "libapparmor1", "libltdl7", "perl"}, + }, + // 17.03.2 - Centos / Rhel7 (two packages) { DockerVersion: "17.03.2", @@ -591,15 +605,28 @@ func (b *DockerBuilder) Build(c *fi.ModelBuilderContext) error { count++ - c.AddTask(&nodetasks.Package{ - Name: dv.Name, - Version: s(dv.Version), - Source: s(dv.Source), - Hash: s(dv.Hash), + if dv.PlainBinary { + c.AddTask(&nodetasks.Archive{ + Name: "docker", + Source: dv.Source, + Hash: dv.Hash, + TargetDir: "/usr/bin/", + StripComponents: 1, + }) - // TODO: PreventStart is now unused? - PreventStart: fi.Bool(true), - }) + c.AddTask(b.buildDockerGroup()) + c.AddTask(b.buildSystemdSocket()) + } else { + c.AddTask(&nodetasks.Package{ + Name: dv.Name, + Version: s(dv.Version), + Source: s(dv.Source), + Hash: s(dv.Hash), + + // TODO: PreventStart is now unused? + PreventStart: fi.Bool(true), + }) + } for _, dep := range dv.Dependencies { c.AddTask(&nodetasks.Package{Name: dep}) @@ -640,6 +667,40 @@ func (b *DockerBuilder) Build(c *fi.ModelBuilderContext) error { return nil } +// buildDockerGroup creates the docker group, which owns the docker.socket +func (b *DockerBuilder) buildDockerGroup() *nodetasks.GroupTask { + return &nodetasks.GroupTask{ + Name: "docker", + System: true, + } +} + +// buildSystemdSocket creates docker.socket, for when we're not installing from a package +func (b *DockerBuilder) buildSystemdSocket() *nodetasks.Service { + manifest := &systemd.Manifest{} + manifest.Set("Unit", "Description", "Docker Socket for the API") + manifest.Set("Unit", "PartOf", "docker.service") + + manifest.Set("Socket", "ListenStream", "/var/run/docker.sock") + manifest.Set("Socket", "SocketMode", "0660") + manifest.Set("Socket", "SocketUser", "root") + manifest.Set("Socket", "SocketGroup", "docker") + + manifest.Set("Install", "WantedBy", "sockets.target") + + manifestString := manifest.Render() + glog.V(8).Infof("Built docker.socket manifest\n%s", manifestString) + + service := &nodetasks.Service{ + Name: "docker.socket", + Definition: s(manifestString), + } + + service.InitDefaults() + + return service +} + func (b *DockerBuilder) buildSystemdService(dockerVersionMajor int64, dockerVersionMinor int64) *nodetasks.Service { oldDocker := dockerVersionMajor <= 1 && dockerVersionMinor <= 11 usesDockerSocket := true diff --git a/upup/pkg/fi/nodeup/nodetasks/BUILD.bazel b/upup/pkg/fi/nodeup/nodetasks/BUILD.bazel index 1000dba903..2f9eb96f4f 100644 --- a/upup/pkg/fi/nodeup/nodetasks/BUILD.bazel +++ b/upup/pkg/fi/nodeup/nodetasks/BUILD.bazel @@ -8,6 +8,7 @@ go_library( "bindmount.go", "createsdir.go", "file.go", + "group.go", "load_image.go", "mount_disk.go", "package.go", diff --git a/upup/pkg/fi/nodeup/nodetasks/archive.go b/upup/pkg/fi/nodeup/nodetasks/archive.go index 37d9dc0901..e722d93015 100644 --- a/upup/pkg/fi/nodeup/nodetasks/archive.go +++ b/upup/pkg/fi/nodeup/nodetasks/archive.go @@ -24,6 +24,7 @@ import ( "os/exec" "path" "reflect" + "strconv" "github.com/golang/glog" "k8s.io/kops/upup/pkg/fi" @@ -43,6 +44,9 @@ type Archive struct { // TargetDir is the directory for extraction TargetDir string `json:"target,omitempty"` + + // StripComponents is the number of components to remove when expanding the archive + StripComponents int `json:"stripComponents,omitempty"` } const ( @@ -159,6 +163,10 @@ func (_ *Archive) RenderLocal(t *local.LocalTarget, a, e, changes *Archive) erro } args := []string{"tar", "xf", localFile, "-C", targetDir} + if e.StripComponents != 0 { + args = append(args, "--strip-components="+strconv.Itoa(e.StripComponents)) + } + glog.Infof("running command %s", args) cmd := exec.Command(args[0], args[1:]...) if output, err := cmd.CombinedOutput(); err != nil { diff --git a/upup/pkg/fi/nodeup/nodetasks/group.go b/upup/pkg/fi/nodeup/nodetasks/group.go new file mode 100644 index 0000000000..a3c598a9fc --- /dev/null +++ b/upup/pkg/fi/nodeup/nodetasks/group.go @@ -0,0 +1,135 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package nodetasks + +import ( + "fmt" + "os/exec" + "strconv" + "strings" + + "github.com/golang/glog" + "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/nodeup/cloudinit" + "k8s.io/kops/upup/pkg/fi/nodeup/local" +) + +// GroupTask is responsible for creating a group, by calling groupadd +type GroupTask struct { + Name string + GID *int + System bool +} + +var _ fi.Task = &GroupTask{} + +func (e *GroupTask) String() string { + return fmt.Sprintf("Group: %s", e.Name) +} + +var _ fi.HasName = &File{} + +func (f *GroupTask) GetName() *string { + return &f.Name +} + +func (f *GroupTask) SetName(name string) { + glog.Fatalf("SetName not supported for Group task") +} + +func (e *GroupTask) Find(c *fi.Context) (*GroupTask, error) { + info, err := fi.LookupGroup(e.Name) + if err != nil { + return nil, err + } + if info == nil { + return nil, nil + } + + gid := info.Gid + actual := &GroupTask{ + Name: e.Name, + GID: &gid, + } + + // Avoid spurious changes + actual.System = e.System + + return actual, nil +} + +func (e *GroupTask) Run(c *fi.Context) error { + return fi.DefaultDeltaRunMethod(e, c) +} + +func (_ *GroupTask) CheckChanges(a, e, changes *GroupTask) error { + return nil +} + +func buildGroupaddArgs(e *GroupTask) []string { + var args []string + if e.GID != nil { + args = append(args, "-g", strconv.Itoa(*e.GID)) + } + if e.System { + args = append(args, "--system") + } + args = append(args, e.Name) + return args +} + +func (_ *GroupTask) RenderLocal(t *local.LocalTarget, a, e, changes *GroupTask) error { + if a == nil { + args := buildGroupaddArgs(e) + glog.Infof("Creating group %q", e.Name) + cmd := exec.Command("groupadd", args...) + glog.V(2).Infof("running command: groupadd %s", strings.Join(args, " ")) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("error creating group: %v\nOutput: %s", err, output) + } + } else { + var args []string + + if changes.GID != nil { + args = append(args, "-g", strconv.Itoa(*e.GID)) + } + + if len(args) != 0 { + args = append(args, e.Name) + glog.Infof("Reconfiguring group %q", e.Name) + cmd := exec.Command("groupmod", args...) + glog.V(2).Infof("running command: groupmod %s", strings.Join(args, " ")) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("error reconfiguring group: %v\nOutput: %s", err, output) + } + } + } + + return nil +} + +func (_ *GroupTask) RenderCloudInit(t *cloudinit.CloudInitTarget, a, e, changes *GroupTask) error { + args := buildGroupaddArgs(e) + cmd := []string{"groupadd"} + cmd = append(cmd, args...) + glog.Infof("Creating group %q", e.Name) + t.AddCommand(cloudinit.Once, cmd...) + + return nil +} diff --git a/upup/pkg/fi/nodeup/nodetasks/service.go b/upup/pkg/fi/nodeup/nodetasks/service.go index d0e1097c58..1a0f4e5bcf 100644 --- a/upup/pkg/fi/nodeup/nodetasks/service.go +++ b/upup/pkg/fi/nodeup/nodetasks/service.go @@ -70,7 +70,7 @@ func (p *Service) GetDependencies(tasks map[string]fi.Task) []fi.Task { // launching a custom Kubernetes build), they all depend on // the "docker.service" Service task. switch v.(type) { - case *File, *Package, *UpdatePackages, *UserTask, *MountDiskTask: + case *File, *Package, *UpdatePackages, *UserTask, *GroupTask, *MountDiskTask: deps = append(deps, v) case *Service, *LoadImageTask: // ignore