Added resource limits/requests flags to helm init

This PR adds the following 4 additional flags to be used with helm init
command so that appropriate CPU/Memory limits can be set for tiller
container.
--tiller-cpu-limits
--tiller-cpu-requests
--tiller-memory-limits
--tiller-memory-requests

To maintain backwards compatibility, when none of these flags are set,
no limitrange will be added. When tiller-cpu-limits is specified but
not the tiller-cpu-requests, then tiller-cpu-requests is set to same
value as the tiller-cpu-limits. But not vice-versa. Same applies to
tiller-memory-limits and tiller-memory-requests.

Fixes: https://github.com/kubernetes/helm/issues/2135
reviewable/pr2632/r1
Aishwarya Thangappa 8 years ago
parent dfb296ad77
commit 10fd6f5c98

@ -26,6 +26,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/kubernetes"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/helm/cmd/helm/installer"
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath"
@ -79,6 +80,10 @@ type initCmd struct {
opts installer.Options
kubeClient kubernetes.Interface
serviceAccount string
cpuLimits string
cpuRequests string
memLimits string
memRequests string
}
func newInitCmd(out io.Writer) *cobra.Command {
@ -120,6 +125,10 @@ func newInitCmd(out io.Writer) *cobra.Command {
f.BoolVar(&i.opts.EnableHostNetwork, "net-host", false, "install Tiller with net=host")
f.StringVar(&i.serviceAccount, "service-account", "", "name of service account")
f.StringVar(&i.cpuLimits, "tiller-cpu-limits", "", "override tiller cpu limits")
f.StringVar(&i.cpuRequests, "tiller-cpu-requests", "", "override tiller cpu requests")
f.StringVar(&i.memLimits, "tiller-memory-limits", "", "override tiller cpu limits")
f.StringVar(&i.memRequests, "tiller-memory-requests", "", "override tiller cpu limits")
return cmd
}
@ -149,16 +158,50 @@ func (i *initCmd) tlsOptions() error {
return nil
}
// run initializes local config and installs Tiller to Kubernetes cluster.
func (i *initCmd) generateLimits() error {
var err error
if i.cpuLimits != "" {
i.opts.CPULimits, err = resource.ParseQuantity(i.cpuLimits)
if err != nil {
return err
}
}
if i.cpuRequests != "" {
i.opts.CPURequests, err = resource.ParseQuantity(i.cpuRequests)
if err != nil {
return err
}
}
if i.memLimits != "" {
i.opts.MemLimits, err = resource.ParseQuantity(i.memLimits)
if err != nil {
return err
}
}
if i.memRequests != "" {
i.opts.MemRequests, err = resource.ParseQuantity(i.memRequests)
if err != nil {
return err
}
}
return nil
}
// run initializes local config and installs tiller to Kubernetes Cluster.
func (i *initCmd) run() error {
if err := i.tlsOptions(); err != nil {
return err
}
if err := i.generateLimits(); err != nil {
return err
}
i.opts.Namespace = i.namespace
i.opts.UseCanary = i.canary
i.opts.ImageSpec = i.image
i.opts.ServiceAccount = i.serviceAccount
if settings.Debug {
writeYAMLManifest := func(apiVersion, kind, body string, first, last bool) error {
w := i.out

@ -302,3 +302,33 @@ func TestInitCmd_tlsOptions(t *testing.T) {
}
}
}
func TestInitCmd_resourceLimits(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
defer os.Remove(home)
var buf bytes.Buffer
fc := fake.NewSimpleClientset()
cmd := &initCmd{
out: &buf,
home: helmpath.Home(home),
kubeClient: fc,
clientOnly: true,
dryRun: true,
namespace: v1.NamespaceDefault,
cpuLimits: "2",
cpuRequests: "100m",
memLimits: "1Gi",
memRequests: "500Mi",
}
if err := cmd.run(); err != nil {
t.Fatal(err)
}
if len(fc.Actions()) != 0 {
t.Error("expected client call")
}
}

@ -21,6 +21,7 @@ import (
"github.com/ghodss/yaml"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
@ -116,8 +117,43 @@ func generateLabels(labels map[string]string) map[string]string {
return labels
}
func generateLimits(opts *Options) (v1.ResourceRequirements, bool) {
limits := map[v1.ResourceName]resource.Quantity{}
requests := map[v1.ResourceName]resource.Quantity{}
resources := v1.ResourceRequirements{}
var resourcesEnabled bool
if (opts.CPURequests != (resource.Quantity{})) || (opts.CPULimits != (resource.Quantity{})) ||
(opts.MemRequests != (resource.Quantity{})) || opts.MemLimits != (resource.Quantity{}) {
resourcesEnabled = true
if opts.CPULimits != (resource.Quantity{}) {
limits[v1.ResourceCPU] = opts.CPULimits
if opts.CPURequests == (resource.Quantity{}) {
requests[v1.ResourceCPU] = opts.CPULimits
}
}
if opts.CPURequests != (resource.Quantity{}) {
requests[v1.ResourceCPU] = opts.CPURequests
}
if opts.MemLimits != (resource.Quantity{}) {
limits[v1.ResourceMemory] = opts.MemLimits
if opts.MemRequests == (resource.Quantity{}) {
requests[v1.ResourceMemory] = opts.MemLimits
}
}
if opts.MemRequests != (resource.Quantity{}) {
requests[v1.ResourceMemory] = opts.MemRequests
}
resources = v1.ResourceRequirements{
Limits: limits,
Requests: requests,
}
}
return resources, resourcesEnabled
}
func generateDeployment(opts *Options) *v1beta1.Deployment {
labels := generateLabels(map[string]string{"name": "tiller"})
resources, resourcesEnabled := generateLimits(opts)
d := &v1beta1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: opts.Namespace,
@ -172,7 +208,9 @@ func generateDeployment(opts *Options) *v1beta1.Deployment {
},
},
}
if resourcesEnabled {
d.Spec.Template.Spec.Containers[0].Resources = resources
}
if opts.tls() {
const certsDir = "/etc/certs"

@ -30,6 +30,8 @@ import (
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
testcore "k8s.io/client-go/testing"
"fmt"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/helm/pkg/version"
)
@ -144,6 +146,91 @@ func TestDeploymentManifest_WithTLS(t *testing.T) {
}
}
func TestDeploymentManifest_WithResourceLimits(t *testing.T) {
type test struct {
cpuLimits string
cpuRequests string
memLimits string
memRequests string
}
var tests = []test{
{
cpuLimits: "2",
cpuRequests: "100m",
memLimits: "1Gi",
memRequests: "500Mi",
},
{
cpuLimits: "2",
memLimits: "1Gi",
},
{
cpuRequests: "100m",
memRequests: "500Mi",
},
{},
}
for _, tt := range tests {
opts := &Options{Namespace: v1.NamespaceDefault}
var err error
if tt.cpuLimits != "" {
opts.CPULimits, err = resource.ParseQuantity(tt.cpuLimits)
if err != nil {
t.Errorf("Error %q", err)
}
}
if tt.cpuRequests != "" {
opts.CPURequests, err = resource.ParseQuantity(tt.cpuRequests)
if err != nil {
t.Errorf("Error %q", err)
}
}
if tt.memLimits != "" {
opts.MemLimits, err = resource.ParseQuantity(tt.memLimits)
if err != nil {
t.Errorf("Error %q", err)
}
}
if tt.memRequests != "" {
opts.MemRequests, err = resource.ParseQuantity(tt.memRequests)
if err != nil {
t.Errorf("Error %q", err)
}
}
o, err := DeploymentManifest(opts)
if err != nil {
t.Fatalf("error %q", err)
}
var d v1beta1.Deployment
if err := yaml.Unmarshal([]byte(o), &d); err != nil {
t.Fatalf(" error %q", err)
}
fmt.Println(o)
// verify Resources in deployment reflect the use of cpu/memory limits.
if got := d.Spec.Template.Spec.Containers[0].Resources.Limits[v1.ResourceCPU]; got != opts.CPULimits {
t.Errorf("Expected cpu limits %q, got %q", opts.CPULimits, got)
}
if got := d.Spec.Template.Spec.Containers[0].Resources.Limits[v1.ResourceMemory]; got != opts.MemLimits {
t.Errorf("Expected memory limits %q, got %q", opts.MemLimits, got)
}
if got := d.Spec.Template.Spec.Containers[0].Resources.Requests[v1.ResourceCPU]; got != opts.CPURequests && got != opts.CPULimits {
t.Errorf("Expected cpu requests %q, got %q", opts.CPURequests, got)
}
if got := d.Spec.Template.Spec.Containers[0].Resources.Requests[v1.ResourceMemory]; got != opts.MemRequests && got != opts.MemLimits {
t.Errorf("Expected memory requests %q, got %q", opts.MemRequests, got)
}
}
}
func TestServiceManifest(t *testing.T) {
o, err := ServiceManifest(v1.NamespaceDefault)
if err != nil {

@ -19,6 +19,7 @@ package installer // import "k8s.io/helm/cmd/helm/installer"
import (
"fmt"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/helm/pkg/version"
)
@ -71,6 +72,18 @@ type Options struct {
// EnableHostNetwork installs Tiller with net=host.
EnableHostNetwork bool
//CPULimits is the cpu limits used to deploy tiller
CPULimits resource.Quantity
//CPURequests is the cpu request limits used to deploy tiller
CPURequests resource.Quantity
//MemLimits is the memory limits used to deploy tiller
MemLimits resource.Quantity
//MemRequests is the memory request limits used to deploy tiller
MemRequests resource.Quantity
}
func (opts *Options) selectImage() string {

Loading…
Cancel
Save