From ad7b10bf21e6a8493ec6d9b26c7e26b050878013 Mon Sep 17 00:00:00 2001 From: derekwaynecarr Date: Mon, 1 Jun 2015 14:05:05 -0400 Subject: [PATCH 1/8] Name is a required parameter for kubectl run, default-name is not (cherry picked from commit b69f1ff5bd7e93e13e773a3cabdd597a94cbfb28) Conflicts: pkg/kubectl/run.go --- pkg/kubectl/run.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/kubectl/run.go b/pkg/kubectl/run.go index 3d4906e82f..5e5652bd84 100644 --- a/pkg/kubectl/run.go +++ b/pkg/kubectl/run.go @@ -29,6 +29,7 @@ type BasicReplicationController struct{} func (BasicReplicationController) ParamNames() []GeneratorParam { return []GeneratorParam{ {"labels", false}, + {"default-name", false}, {"name", true}, {"replicas", true}, {"image", true}, From 39c308012a3f6f2df886d828abe2f1f99f639796 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Tue, 19 May 2015 15:52:12 -0700 Subject: [PATCH 2/8] Have the ContainerManager create a system container. The system container is a resource-only container which contains all non-kernel processes that are not already part of a container. This will allow monitoring of their resource usage and limiting it (eventually). (cherry picked from commit ddec34a000ad9db1547fcae8324efa7f0b78fd25) --- pkg/kubelet/container_manager.go | 1 + pkg/kubelet/container_manager_linux.go | 112 ++++++++++++++++--- pkg/kubelet/container_manager_unsupported.go | 2 +- pkg/kubelet/kubelet.go | 8 +- 4 files changed, 107 insertions(+), 16 deletions(-) diff --git a/pkg/kubelet/container_manager.go b/pkg/kubelet/container_manager.go index 84b20de18d..188646c45d 100644 --- a/pkg/kubelet/container_manager.go +++ b/pkg/kubelet/container_manager.go @@ -20,5 +20,6 @@ package kubelet type containerManager interface { // Runs the container manager's housekeeping. // - Ensures that the Docker daemon is in a container. + // - Creates the system container where all non-containerized processes run. Start() error } diff --git a/pkg/kubelet/container_manager_linux.go b/pkg/kubelet/container_manager_linux.go index 330e4bf43f..af1d53a500 100644 --- a/pkg/kubelet/container_manager_linux.go +++ b/pkg/kubelet/container_manager_linux.go @@ -35,33 +35,60 @@ import ( ) type containerManagerImpl struct { - // Absolute name of the desired container that Docker should be in. - dockerContainerName string + // Whether to create and use the specified containers. + useDockerContainer bool + useSystemContainer bool - // The manager of the resource-only container Docker should be in. - manager fs.Manager + // OOM score for the Docker container. dockerOomScoreAdj int + + // Managers for containers. + dockerContainer fs.Manager + systemContainer fs.Manager + rootContainer fs.Manager } var _ containerManager = &containerManagerImpl{} -// Takes the absolute name that the Docker daemon should be in. -// Empty container name disables moving the Docker daemon. -func newContainerManager(dockerDaemonContainer string) (containerManager, error) { +// Takes the absolute name of the specified containers. +// Empty container name disables use of the specified container. +func newContainerManager(dockerDaemonContainer, systemContainer string) (containerManager, error) { + if systemContainer == "/" { + return nil, fmt.Errorf("system container cannot be root (\"/\")") + } + return &containerManagerImpl{ - dockerContainerName: dockerDaemonContainer, - manager: fs.Manager{ + useDockerContainer: dockerDaemonContainer != "", + useSystemContainer: systemContainer != "", + dockerOomScoreAdj: -900, + dockerContainer: fs.Manager{ Cgroups: &configs.Cgroup{ Name: dockerDaemonContainer, AllowAllDevices: true, }, }, - dockerOomScoreAdj: -900, + systemContainer: fs.Manager{ + Cgroups: &configs.Cgroup{ + Name: systemContainer, + AllowAllDevices: true, + }, + }, + rootContainer: fs.Manager{ + Cgroups: &configs.Cgroup{ + Name: "/", + }, + }, }, nil } func (cm *containerManagerImpl) Start() error { - if cm.dockerContainerName != "" { + if cm.useSystemContainer { + err := cm.ensureSystemContainer() + if err != nil { + return err + } + } + if cm.useDockerContainer { go util.Until(func() { err := cm.ensureDockerInContainer() if err != nil { @@ -99,10 +126,10 @@ func (cm *containerManagerImpl) ensureDockerInContainer() error { errs = append(errs, fmt.Errorf("failed to find container of PID %q: %v", pid, err)) } - if cont != cm.dockerContainerName { - err = cm.manager.Apply(pid) + if cont != cm.dockerContainer.Cgroups.Name { + err = cm.dockerContainer.Apply(pid) if err != nil { - errs = append(errs, fmt.Errorf("failed to move PID %q (in %q) to %q", pid, cont, cm.dockerContainerName)) + errs = append(errs, fmt.Errorf("failed to move PID %q (in %q) to %q", pid, cont, cm.dockerContainer.Cgroups.Name)) } } @@ -125,3 +152,60 @@ func getContainer(pid int) (string, error) { return cgroups.ParseCgroupFile("cpu", f) } + +// Ensures the system container is created and all non-kernel processes without +// a container are moved to it. +func (cm *containerManagerImpl) ensureSystemContainer() error { + // Move non-kernel PIDs to the system container. + attemptsRemaining := 10 + var errs []error + for attemptsRemaining >= 0 { + // Only keep errors on latest attempt. + errs = []error{} + attemptsRemaining-- + + allPids, err := cm.rootContainer.GetPids() + if err != nil { + errs = append(errs, fmt.Errorf("Failed to list PIDs for root: %v", err)) + continue + } + + // Remove kernel pids + pids := make([]int, 0, len(allPids)) + for _, pid := range allPids { + if isKernelPid(pid) { + continue + } + + pids = append(pids, pid) + } + glog.Infof("Found %d PIDs in root, %d of them are kernel related", len(allPids), len(allPids)-len(pids)) + + // Check if we moved all the non-kernel PIDs. + if len(pids) == 0 { + break + } + + glog.Infof("Moving non-kernel threads: %v", pids) + for _, pid := range pids { + err := cm.systemContainer.Apply(pid) + if err != nil { + errs = append(errs, fmt.Errorf("failed to move PID %d into the system container %q: %v", pid, cm.systemContainer.Cgroups.Name, err)) + continue + } + } + + } + if attemptsRemaining < 0 { + errs = append(errs, fmt.Errorf("ran out of attempts to create system containers %q", cm.systemContainer.Cgroups.Name)) + } + + return errors.NewAggregate(errs) +} + +// Determines whether the specified PID is a kernel PID. +func isKernelPid(pid int) bool { + // Kernel threads have no associated executable. + _, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", pid)) + return err != nil +} diff --git a/pkg/kubelet/container_manager_unsupported.go b/pkg/kubelet/container_manager_unsupported.go index 6c543e1e61..77246f174a 100644 --- a/pkg/kubelet/container_manager_unsupported.go +++ b/pkg/kubelet/container_manager_unsupported.go @@ -31,6 +31,6 @@ func (unsupportedContainerManager) Start() error { return fmt.Errorf("Container Manager is unsupported in this build") } -func newContainerManager(dockerDaemonContainer string) (containerManager, error) { +func newContainerManager(dockerDaemonContainer, systemContainer string) (containerManager, error) { return &unsupportedContainerManager{}, nil } diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 7614a1149d..cd7b394073 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -147,6 +147,9 @@ func NewMainKubelet( if resyncInterval <= 0 { return nil, fmt.Errorf("invalid sync frequency %d", resyncInterval) } + if systemContainer != "" && cgroupRoot == "" { + return nil, fmt.Errorf("invalid configuration: system container was specified and cgroup root was not specified") + } dockerClient = dockertools.NewInstrumentedDockerInterface(dockerClient) serviceStore := cache.NewStore(cache.MetaNamespaceKeyFunc) @@ -295,7 +298,10 @@ func NewMainKubelet( return nil, fmt.Errorf("unsupported container runtime %q specified", containerRuntime) } - containerManager, err := newContainerManager(dockerDaemonContainer) + // TODO(vmarmol): Make configurable. + // Setup container manager, can fail if the devices hierarchy is not mounted + // (it is required by Docker however). + containerManager, err := newContainerManager(dockerDaemonContainer, "/system") if err != nil { return nil, fmt.Errorf("failed to create the Container Manager: %v", err) } From 220570dfc15d4175962d3d9a179d5cad58f0a36c Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Tue, 19 May 2015 16:19:12 -0700 Subject: [PATCH 3/8] Make system container name configurable. (cherry picked from commit 9a2630ac6ed4bb0645f4668b5cdfe2a6980a71d5) --- cmd/kubelet/app/server.go | 8 +++++++- pkg/kubelet/kubelet.go | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index e9845700fc..0c6d75cc30 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -108,6 +108,7 @@ type KubeletServer struct { CgroupRoot string ContainerRuntime string DockerDaemonContainer string + SystemContainer string ConfigureCBR0 bool MaxPods int @@ -170,6 +171,7 @@ func NewKubeletServer() *KubeletServer { CgroupRoot: "", ContainerRuntime: "docker", DockerDaemonContainer: "/docker-daemon", + SystemContainer: "", ConfigureCBR0: false, } } @@ -228,7 +230,7 @@ func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&s.ResourceContainer, "resource-container", s.ResourceContainer, "Absolute name of the resource-only container to create and run the Kubelet in (Default: /kubelet).") fs.StringVar(&s.CgroupRoot, "cgroup_root", s.CgroupRoot, "Optional root cgroup to use for pods. This is handled by the container runtime on a best effort basis. Default: '', which means use the container runtime default.") fs.StringVar(&s.ContainerRuntime, "container_runtime", s.ContainerRuntime, "The container runtime to use. Possible values: 'docker', 'rkt'. Default: 'docker'.") - fs.StringVar(&s.DockerDaemonContainer, "docker-daemon-container", s.DockerDaemonContainer, "Optional resource-only container in which to place the Docker Daemon. Empty for no container (Default: /docker-daemon).") + fs.StringVar(&s.SystemContainer, "system-container", s.SystemContainer, "Optional resource-only container in which to place all non-kernel processes that are not already in a container. Empty for no container. Rolling back the flag requires a reboot. (Default: \"\").") fs.BoolVar(&s.ConfigureCBR0, "configure-cbr0", s.ConfigureCBR0, "If true, kubelet will configure cbr0 based on Node.Spec.PodCIDR.") fs.IntVar(&s.MaxPods, "max-pods", 100, "Number of Pods that can run on this Kubelet.") @@ -347,6 +349,7 @@ func (s *KubeletServer) Run(_ []string) error { ContainerRuntime: s.ContainerRuntime, Mounter: mounter, DockerDaemonContainer: s.DockerDaemonContainer, + SystemContainer: s.SystemContainer, ConfigureCBR0: s.ConfigureCBR0, MaxPods: s.MaxPods, } @@ -513,6 +516,7 @@ func SimpleKubelet(client *client.Client, ContainerRuntime: "docker", Mounter: mount.New(), DockerDaemonContainer: "/docker-daemon", + SystemContainer: "", MaxPods: 32, } return &kcfg @@ -648,6 +652,7 @@ type KubeletConfig struct { ContainerRuntime string Mounter mount.Interface DockerDaemonContainer string + SystemContainer string ConfigureCBR0 bool MaxPods int } @@ -701,6 +706,7 @@ func createAndInitKubelet(kc *KubeletConfig) (k KubeletBootstrap, pc *config.Pod kc.ContainerRuntime, kc.Mounter, kc.DockerDaemonContainer, + kc.SystemContainer, kc.ConfigureCBR0, kc.MaxPods) diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index cd7b394073..ad2c09feb7 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -139,6 +139,7 @@ func NewMainKubelet( containerRuntime string, mounter mount.Interface, dockerDaemonContainer string, + systemContainer string, configureCBR0 bool, pods int) (*Kubelet, error) { if rootDirectory == "" { @@ -298,10 +299,9 @@ func NewMainKubelet( return nil, fmt.Errorf("unsupported container runtime %q specified", containerRuntime) } - // TODO(vmarmol): Make configurable. // Setup container manager, can fail if the devices hierarchy is not mounted // (it is required by Docker however). - containerManager, err := newContainerManager(dockerDaemonContainer, "/system") + containerManager, err := newContainerManager(dockerDaemonContainer, systemContainer) if err != nil { return nil, fmt.Errorf("failed to create the Container Manager: %v", err) } From 74468bb3b7aeccb98b51e1e66ee005c278659ba0 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Fri, 22 May 2015 09:55:30 -0700 Subject: [PATCH 4/8] Create a /system system container in Debian. (cherry picked from commit c97dda068dd96ab3ca759453a1e77791c98eb257) --- cluster/saltbase/salt/kubelet/default | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cluster/saltbase/salt/kubelet/default b/cluster/saltbase/salt/kubelet/default index e34369ea1b..02c39c4982 100644 --- a/cluster/saltbase/salt/kubelet/default +++ b/cluster/saltbase/salt/kubelet/default @@ -58,10 +58,12 @@ {% set configure_cbr0 = "--configure-cbr0=" + pillar['allocate_node_cidrs'] -%} {% endif -%} -# Run containers under the root cgroup. +# Run containers under the root cgroup and create a system container. +{% set system_container = "" -%} {% set cgroup_root = "" -%} {% if grains['os_family'] == 'Debian' -%} + {% set system_container = "--system-container=/system" -%} {% set cgroup_root = "--cgroup_root=/" -%} {% endif -%} -DAEMON_ARGS="{{daemon_args}} {{api_servers_with_port}} {{hostname_override}} {{cloud_provider}} {{config}} --allow_privileged={{pillar['allow_privileged']}} {{pillar['log_level']}} {{cluster_dns}} {{cluster_domain}} {{docker_root}} {{configure_cbr0}} {{cgroup_root}}" +DAEMON_ARGS="{{daemon_args}} {{api_servers_with_port}} {{hostname_override}} {{cloud_provider}} {{config}} --allow_privileged={{pillar['allow_privileged']}} {{pillar['log_level']}} {{cluster_dns}} {{cluster_domain}} {{docker_root}} {{configure_cbr0}} {{cgroup_root}} {{system_container}}" From b2a38313619ee7020167036f6044af6d246f3711 Mon Sep 17 00:00:00 2001 From: Zach Loafman Date: Tue, 2 Jun 2015 07:01:23 -0700 Subject: [PATCH 5/8] gke provider: Fix "gcloud .* describe" checks in util.sh This basically prevents you from re-running e2es cross-project on the GKE provider. (The current Jenkins jobs that have been moved to different projects are working kind of accidentally.) (cherry picked from commit 6f1d60ed3117064a8c2fe6924dcb1fca7e3be258) --- cluster/gke/util.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster/gke/util.sh b/cluster/gke/util.sh index b79e949ee3..be01f35dda 100755 --- a/cluster/gke/util.sh +++ b/cluster/gke/util.sh @@ -116,7 +116,7 @@ function kube-up() { detect-project >&2 # Make the specified network if we need to. - if ! gcloud compute networks describe "${NETWORK}" &>/dev/null; then + if ! gcloud compute networks --project "${PROJECT}" describe "${NETWORK}" &>/dev/null; then echo "Creating new network: ${NETWORK}" >&2 gcloud compute networks create "${NETWORK}" --project="${PROJECT}" --range "${NETWORK_RANGE}" else @@ -125,7 +125,7 @@ function kube-up() { # Allow SSH on all nodes in the network. This doesn't actually check whether # such a rule exists, only whether we've created this exact rule. - if ! gcloud compute firewall-rules describe "${FIREWALL_SSH}" &>/dev/null; then + if ! gcloud compute firewall-rules --project "${PROJECT}" describe "${FIREWALL_SSH}" &>/dev/null; then echo "Creating new firewall for SSH: ${FIREWALL_SSH}" >&2 gcloud compute firewall-rules create "${FIREWALL_SSH}" \ --allow="tcp:22" \ From deb907180b7e4dde179fcba87dfefc3c8d0d83a4 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Tue, 2 Jun 2015 19:45:51 -0700 Subject: [PATCH 6/8] Fix a bug in kubectl exec handling. (cherry picked from commit 911e3e95945cc2c43787ab059f14d0fbcc88bcb3) --- pkg/kubectl/cmd/exec.go | 24 ++++++++++++----------- pkg/kubectl/cmd/exec_test.go | 38 +++++++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/pkg/kubectl/cmd/exec.go b/pkg/kubectl/cmd/exec.go index 5febff45ef..787b6ed0ff 100644 --- a/pkg/kubectl/cmd/exec.go +++ b/pkg/kubectl/cmd/exec.go @@ -80,27 +80,29 @@ type execParams struct { tty bool } -func extractPodAndContainer(cmd *cobra.Command, args []string, p *execParams) (podName string, containerName string, err error) { - if len(p.podName) == 0 && len(args) == 0 { - return "", "", cmdutil.UsageError(cmd, "POD is required for exec") +func extractPodAndContainer(cmd *cobra.Command, argsIn []string, p *execParams) (podName string, containerName string, args []string, err error) { + if len(p.podName) == 0 && len(argsIn) == 0 { + return "", "", nil, cmdutil.UsageError(cmd, "POD is required for exec") } if len(p.podName) != 0 { printDeprecationWarning("exec POD", "-p POD") podName = p.podName - if len(args) < 1 { - return "", "", cmdutil.UsageError(cmd, "COMMAND is required for exec") + if len(argsIn) < 1 { + return "", "", nil, cmdutil.UsageError(cmd, "COMMAND is required for exec") } + args = argsIn } else { - podName = args[0] - if len(args) < 2 { - return "", "", cmdutil.UsageError(cmd, "COMMAND is required for exec") + podName = argsIn[0] + args = argsIn[1:] + if len(args) < 1 { + return "", "", nil, cmdutil.UsageError(cmd, "COMMAND is required for exec") } } - return podName, p.containerName, nil + return podName, p.containerName, args, nil } -func RunExec(f *cmdutil.Factory, cmd *cobra.Command, cmdIn io.Reader, cmdOut, cmdErr io.Writer, p *execParams, args []string, re remoteExecutor) error { - podName, containerName, err := extractPodAndContainer(cmd, args, p) +func RunExec(f *cmdutil.Factory, cmd *cobra.Command, cmdIn io.Reader, cmdOut, cmdErr io.Writer, p *execParams, argsIn []string, re remoteExecutor) error { + podName, containerName, args, err := extractPodAndContainer(cmd, argsIn, p) namespace, err := f.DefaultNamespace() if err != nil { return err diff --git a/pkg/kubectl/cmd/exec_test.go b/pkg/kubectl/cmd/exec_test.go index 05e5f9ecb7..31a5e44846 100644 --- a/pkg/kubectl/cmd/exec_test.go +++ b/pkg/kubectl/cmd/exec_test.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "net/http" + "reflect" "testing" "github.com/spf13/cobra" @@ -43,58 +44,73 @@ func TestPodAndContainer(t *testing.T) { tests := []struct { args []string p *execParams + name string expectError bool expectedPod string expectedContainer string + expectedArgs []string }{ { p: &execParams{}, expectError: true, + name: "empty", }, { p: &execParams{podName: "foo"}, expectError: true, + name: "no cmd", }, { p: &execParams{podName: "foo", containerName: "bar"}, expectError: true, + name: "no cmd, w/ container", }, { - p: &execParams{podName: "foo"}, - args: []string{"cmd"}, - expectedPod: "foo", + p: &execParams{podName: "foo"}, + args: []string{"cmd"}, + expectedPod: "foo", + expectedArgs: []string{"cmd"}, + name: "pod in flags", }, { p: &execParams{}, args: []string{"foo"}, expectError: true, + name: "no cmd, w/o flags", }, { - p: &execParams{}, - args: []string{"foo", "cmd"}, - expectedPod: "foo", + p: &execParams{}, + args: []string{"foo", "cmd"}, + expectedPod: "foo", + expectedArgs: []string{"cmd"}, + name: "cmd, w/o flags", }, { p: &execParams{containerName: "bar"}, args: []string{"foo", "cmd"}, expectedPod: "foo", expectedContainer: "bar", + expectedArgs: []string{"cmd"}, + name: "cmd, container in flag", }, } for _, test := range tests { cmd := &cobra.Command{} - podName, containerName, err := extractPodAndContainer(cmd, test.args, test.p) + podName, containerName, args, err := extractPodAndContainer(cmd, test.args, test.p) if podName != test.expectedPod { - t.Errorf("expected: %s, got: %s", test.expectedPod, podName) + t.Errorf("expected: %s, got: %s (%s)", test.expectedPod, podName, test.name) } if containerName != test.expectedContainer { - t.Errorf("expected: %s, got: %s", test.expectedContainer, containerName) + t.Errorf("expected: %s, got: %s (%s)", test.expectedContainer, containerName, test.name) } if test.expectError && err == nil { - t.Error("unexpected non-error") + t.Errorf("unexpected non-error (%s)", test.name) } if !test.expectError && err != nil { - t.Errorf("unexpected error: %v", err) + t.Errorf("unexpected error: %v (%s)", err, test.name) + } + if !reflect.DeepEqual(test.expectedArgs, args) { + t.Errorf("expected: %v, got %v (%s)", test.expectedArgs, args, test.name) } } } From befd1385e5af5f7516f75a27a2628272bb9e9f36 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Tue, 2 Jun 2015 20:30:14 -0700 Subject: [PATCH 7/8] Kubernetes version v0.18.1 --- pkg/version/base.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/version/base.go b/pkg/version/base.go index 5baa89bf11..bd0929d594 100644 --- a/pkg/version/base.go +++ b/pkg/version/base.go @@ -36,8 +36,8 @@ package version var ( // TODO: Deprecate gitMajor and gitMinor, use only gitVersion instead. gitMajor string = "0" // major version, always numeric - gitMinor string = "18.0" // minor version, numeric possibly followed by "+" - gitVersion string = "v0.18.0" // version from git, output of $(git describe) + gitMinor string = "18.1" // minor version, numeric possibly followed by "+" + gitVersion string = "v0.18.1" // version from git, output of $(git describe) gitCommit string = "" // sha1 from git, output of $(git rev-parse HEAD) gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty" ) From 3e5707be5bd051d96b8ba96ed50273e3b6f2dae5 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Tue, 2 Jun 2015 20:30:49 -0700 Subject: [PATCH 8/8] Kubernetes version v0.18.1-dev --- pkg/version/base.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/version/base.go b/pkg/version/base.go index bd0929d594..11f57fbdc0 100644 --- a/pkg/version/base.go +++ b/pkg/version/base.go @@ -36,8 +36,8 @@ package version var ( // TODO: Deprecate gitMajor and gitMinor, use only gitVersion instead. gitMajor string = "0" // major version, always numeric - gitMinor string = "18.1" // minor version, numeric possibly followed by "+" - gitVersion string = "v0.18.1" // version from git, output of $(git describe) + gitMinor string = "18.1+" // minor version, numeric possibly followed by "+" + gitVersion string = "v0.18.1-dev" // version from git, output of $(git describe) gitCommit string = "" // sha1 from git, output of $(git rev-parse HEAD) gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty" )