Commit d7cb50bf authored by Brian's avatar Brian Committed by GitHub

Merge pull request #2144 from fibonacci1729/feat/helm-tls

helm (client/deployment) support for TLS
parents 1c1bfb27 ad614b91
...@@ -45,6 +45,14 @@ const ( ...@@ -45,6 +45,14 @@ const (
tillerNamespaceEnvVar = "TILLER_NAMESPACE" tillerNamespaceEnvVar = "TILLER_NAMESPACE"
) )
var (
tlsCaCertFile string // path to TLS CA certificate file
tlsCertFile string // path to TLS certificate file
tlsKeyFile string // path to TLS key file
tlsVerify bool // enable TLS and verify remote certificates
tlsEnable bool // enable TLS
)
var ( var (
helmHome string helmHome string
tillerHost string tillerHost string
......
...@@ -70,6 +70,7 @@ type initCmd struct { ...@@ -70,6 +70,7 @@ type initCmd struct {
dryRun bool dryRun bool
out io.Writer out io.Writer
home helmpath.Home home helmpath.Home
opts installer.Options
kubeClient internalclientset.Interface kubeClient internalclientset.Interface
} }
...@@ -99,25 +100,73 @@ func newInitCmd(out io.Writer) *cobra.Command { ...@@ -99,25 +100,73 @@ func newInitCmd(out io.Writer) *cobra.Command {
f.BoolVarP(&i.clientOnly, "client-only", "c", false, "if set does not install tiller") f.BoolVarP(&i.clientOnly, "client-only", "c", false, "if set does not install tiller")
f.BoolVar(&i.dryRun, "dry-run", false, "do not install local or remote") f.BoolVar(&i.dryRun, "dry-run", false, "do not install local or remote")
// f.BoolVar(&tlsEnable, "tiller-tls", false, "install tiller with TLS enabled")
// f.BoolVar(&tlsVerify, "tiller-tls-verify", false, "install tiller with TLS enabled and to verify remote certificates")
// f.StringVar(&tlsKeyFile, "tiller-tls-key", "", "path to TLS key file to install with tiller")
// f.StringVar(&tlsCertFile, "tiller-tls-cert", "", "path to TLS certificate file to install with tiller")
// f.StringVar(&tlsCaCertFile, "tls-ca-cert", "", "path to CA root certificate")
return cmd return cmd
} }
// tlsOptions sanitizes the tls flags as well as checks for the existence of required
// tls files indicated by those flags, if any.
func (i *initCmd) tlsOptions() error {
i.opts.EnableTLS = tlsEnable || tlsVerify
i.opts.VerifyTLS = tlsVerify
if i.opts.EnableTLS {
missing := func(file string) bool {
_, err := os.Stat(file)
return os.IsNotExist(err)
}
if i.opts.TLSKeyFile = tlsKeyFile; i.opts.TLSKeyFile == "" || missing(i.opts.TLSKeyFile) {
return errors.New("missing required TLS key file")
}
if i.opts.TLSCertFile = tlsCertFile; i.opts.TLSCertFile == "" || missing(i.opts.TLSCertFile) {
return errors.New("missing required TLS certificate file")
}
if i.opts.VerifyTLS {
if i.opts.TLSCaCertFile = tlsCaCertFile; i.opts.TLSCaCertFile == "" || missing(i.opts.TLSCaCertFile) {
return errors.New("missing required TLS CA file")
}
}
}
return nil
}
// runInit initializes local config and installs tiller to Kubernetes Cluster // runInit initializes local config and installs tiller to Kubernetes Cluster
func (i *initCmd) run() error { func (i *initCmd) run() error {
if err := i.tlsOptions(); err != nil {
return err
}
i.opts.Namespace = i.namespace
i.opts.UseCanary = i.canary
i.opts.ImageSpec = i.image
if flagDebug { if flagDebug {
dm, err := installer.DeploymentManifest(i.namespace, i.image, i.canary) var mfs string
if err != nil { var err error
// write deployment manifest
if mfs, err = installer.DeploymentManifest(&i.opts); err != nil {
return err return err
} }
fm := fmt.Sprintf("apiVersion: extensions/v1beta1\nkind: Deployment\n%s", dm) fmt.Fprintln(i.out, fmt.Sprintf("apiVersion: extensions/v1beta1\nkind: Deployment\n%s", mfs))
fmt.Fprintln(i.out, fm)
sm, err := installer.ServiceManifest(i.namespace) // write service manifest
if err != nil { if mfs, err = installer.ServiceManifest(i.namespace); err != nil {
return err return err
} }
fm = fmt.Sprintf("apiVersion: v1\nkind: Service\n%s", sm) fmt.Fprintln(i.out, fmt.Sprintf("apiVersion: v1\nkind: Service\n%s", mfs))
fmt.Fprintln(i.out, fm)
// write secret manifest
if i.opts.EnableTLS {
if mfs, err = installer.SecretManifest(&i.opts); err != nil {
return err
}
fmt.Fprintln(i.out, fmt.Sprintf("apiVersion: v1\nkind: Secret\n%s", mfs))
}
} }
if i.dryRun { if i.dryRun {
...@@ -143,13 +192,12 @@ func (i *initCmd) run() error { ...@@ -143,13 +192,12 @@ func (i *initCmd) run() error {
} }
i.kubeClient = c i.kubeClient = c
} }
opts := &installer.Options{Namespace: i.namespace, ImageSpec: i.image, UseCanary: i.canary} if err := installer.Install(i.kubeClient, &i.opts); err != nil {
if err := installer.Install(i.kubeClient, opts); err != nil {
if !kerrors.IsAlreadyExists(err) { if !kerrors.IsAlreadyExists(err) {
return fmt.Errorf("error installing: %s", err) return fmt.Errorf("error installing: %s", err)
} }
if i.upgrade { if i.upgrade {
if err := installer.Upgrade(i.kubeClient, opts); err != nil { if err := installer.Upgrade(i.kubeClient, &i.opts); err != nil {
return fmt.Errorf("error when upgrading: %s", err) return fmt.Errorf("error when upgrading: %s", err)
} }
fmt.Fprintln(i.out, "\nTiller (the helm server side component) has been upgraded to the current version.") fmt.Fprintln(i.out, "\nTiller (the helm server side component) has been upgraded to the current version.")
......
...@@ -17,7 +17,7 @@ limitations under the License. ...@@ -17,7 +17,7 @@ limitations under the License.
package installer // import "k8s.io/helm/cmd/helm/installer" package installer // import "k8s.io/helm/cmd/helm/installer"
import ( import (
"fmt" "io/ioutil"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
...@@ -28,22 +28,23 @@ import ( ...@@ -28,22 +28,23 @@ import (
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion" extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion"
"k8s.io/kubernetes/pkg/util/intstr" "k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/helm/pkg/version"
) )
const defaultImage = "gcr.io/kubernetes-helm/tiller"
// Install uses kubernetes client to install tiller. // Install uses kubernetes client to install tiller.
// //
// Returns an error if the command failed. // Returns an error if the command failed.
func Install(client internalclientset.Interface, opts *Options) error { func Install(client internalclientset.Interface, opts *Options) error {
if err := createDeployment(client.Extensions(), opts.Namespace, opts.ImageSpec, opts.UseCanary); err != nil { if err := createDeployment(client.Extensions(), opts); err != nil {
return err return err
} }
if err := createService(client.Core(), opts.Namespace); err != nil { if err := createService(client.Core(), opts.Namespace); err != nil {
return err return err
} }
if opts.tls() {
if err := createSecret(client.Core(), opts); err != nil {
return err
}
}
return nil return nil
} }
...@@ -55,7 +56,7 @@ func Upgrade(client internalclientset.Interface, opts *Options) error { ...@@ -55,7 +56,7 @@ func Upgrade(client internalclientset.Interface, opts *Options) error {
if err != nil { if err != nil {
return err return err
} }
obj.Spec.Template.Spec.Containers[0].Image = selectImage(opts.ImageSpec, opts.UseCanary) obj.Spec.Template.Spec.Containers[0].Image = opts.selectImage()
if _, err := client.Extensions().Deployments(opts.Namespace).Update(obj); err != nil { if _, err := client.Extensions().Deployments(opts.Namespace).Update(obj); err != nil {
return err return err
} }
...@@ -73,15 +74,15 @@ func Upgrade(client internalclientset.Interface, opts *Options) error { ...@@ -73,15 +74,15 @@ func Upgrade(client internalclientset.Interface, opts *Options) error {
} }
// createDeployment creates the Tiller deployment reource // createDeployment creates the Tiller deployment reource
func createDeployment(client extensionsclient.DeploymentsGetter, namespace, image string, canary bool) error { func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error {
obj := deployment(namespace, image, canary) obj := deployment(opts)
_, err := client.Deployments(obj.Namespace).Create(obj) _, err := client.Deployments(obj.Namespace).Create(obj)
return err return err
} }
// deployment gets the deployment object that installs Tiller. // deployment gets the deployment object that installs Tiller.
func deployment(namespace, image string, canary bool) *extensions.Deployment { func deployment(opts *Options) *extensions.Deployment {
return generateDeployment(namespace, selectImage(image, canary)) return generateDeployment(opts)
} }
// createService creates the Tiller service resource // createService creates the Tiller service resource
...@@ -96,21 +97,10 @@ func service(namespace string) *api.Service { ...@@ -96,21 +97,10 @@ func service(namespace string) *api.Service {
return generateService(namespace) return generateService(namespace)
} }
func selectImage(image string, canary bool) string {
switch {
case canary:
image = defaultImage + ":canary"
case image == "":
image = fmt.Sprintf("%s:%s", defaultImage, version.Version)
}
return image
}
// DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment // DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment
// resource. // resource.
func DeploymentManifest(namespace, image string, canary bool) (string, error) { func DeploymentManifest(opts *Options) (string, error) {
obj := deployment(namespace, image, canary) obj := deployment(opts)
buf, err := yaml.Marshal(obj) buf, err := yaml.Marshal(obj)
return string(buf), err return string(buf), err
} }
...@@ -129,11 +119,11 @@ func generateLabels(labels map[string]string) map[string]string { ...@@ -129,11 +119,11 @@ func generateLabels(labels map[string]string) map[string]string {
return labels return labels
} }
func generateDeployment(namespace, image string) *extensions.Deployment { func generateDeployment(opts *Options) *extensions.Deployment {
labels := generateLabels(map[string]string{"name": "tiller"}) labels := generateLabels(map[string]string{"name": "tiller"})
d := &extensions.Deployment{ d := &extensions.Deployment{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Namespace: namespace, Namespace: opts.Namespace,
Name: "tiller-deploy", Name: "tiller-deploy",
Labels: labels, Labels: labels,
}, },
...@@ -147,13 +137,13 @@ func generateDeployment(namespace, image string) *extensions.Deployment { ...@@ -147,13 +137,13 @@ func generateDeployment(namespace, image string) *extensions.Deployment {
Containers: []api.Container{ Containers: []api.Container{
{ {
Name: "tiller", Name: "tiller",
Image: image, Image: opts.selectImage(),
ImagePullPolicy: "IfNotPresent", ImagePullPolicy: "IfNotPresent",
Ports: []api.ContainerPort{ Ports: []api.ContainerPort{
{ContainerPort: 44134, Name: "tiller"}, {ContainerPort: 44134, Name: "tiller"},
}, },
Env: []api.EnvVar{ Env: []api.EnvVar{
{Name: "TILLER_NAMESPACE", Value: namespace}, {Name: "TILLER_NAMESPACE", Value: opts.Namespace},
}, },
LivenessProbe: &api.Probe{ LivenessProbe: &api.Probe{
Handler: api.Handler{ Handler: api.Handler{
...@@ -181,6 +171,37 @@ func generateDeployment(namespace, image string) *extensions.Deployment { ...@@ -181,6 +171,37 @@ func generateDeployment(namespace, image string) *extensions.Deployment {
}, },
}, },
} }
if opts.tls() {
const certsDir = "/etc/certs"
var tlsVerify, tlsEnable = "", "1"
if opts.VerifyTLS {
tlsVerify = "1"
}
// Mount secret to "/etc/certs"
d.Spec.Template.Spec.Containers[0].VolumeMounts = append(d.Spec.Template.Spec.Containers[0].VolumeMounts, api.VolumeMount{
Name: "tiller-certs",
ReadOnly: true,
MountPath: certsDir,
})
// Add environment variable required for enabling TLS
d.Spec.Template.Spec.Containers[0].Env = append(d.Spec.Template.Spec.Containers[0].Env, []api.EnvVar{
{Name: "TILLER_TLS_VERIFY", Value: tlsVerify},
{Name: "TILLER_TLS_ENABLE", Value: tlsEnable},
{Name: "TILLER_TLS_CERTS", Value: certsDir},
}...)
// Add secret volume to deployment
d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, api.Volume{
Name: "tiller-certs",
VolumeSource: api.VolumeSource{
Secret: &api.SecretVolumeSource{
SecretName: "tiller-secret",
},
},
})
}
return d return d
} }
...@@ -206,3 +227,54 @@ func generateService(namespace string) *api.Service { ...@@ -206,3 +227,54 @@ func generateService(namespace string) *api.Service {
} }
return s return s
} }
// SecretManifest gets the manifest (as a string) that describes the Tiller Secret resource.
func SecretManifest(opts *Options) (string, error) {
o, err := generateSecret(opts)
if err != nil {
return "", err
}
buf, err := yaml.Marshal(o)
return string(buf), err
}
// createSecret creates the Tiller secret resource.
func createSecret(client internalversion.SecretsGetter, opts *Options) error {
o, err := generateSecret(opts)
if err != nil {
return err
}
_, err = client.Secrets(o.Namespace).Create(o)
return err
}
// generateSecret builds the secret object that hold Tiller secrets.
func generateSecret(opts *Options) (*api.Secret, error) {
const secretName = "tiller-secret"
labels := generateLabels(map[string]string{"name": "tiller"})
secret := &api.Secret{
Type: api.SecretTypeOpaque,
Data: make(map[string][]byte),
ObjectMeta: api.ObjectMeta{
Name: secretName,
Labels: labels,
Namespace: opts.Namespace,
},
}
var err error
if secret.Data["tls.key"], err = read(opts.TLSKeyFile); err != nil {
return nil, err
}
if secret.Data["tls.crt"], err = read(opts.TLSCertFile); err != nil {
return nil, err
}
if opts.VerifyTLS {
if secret.Data["ca.crt"], err = read(opts.TLSCaCertFile); err != nil {
return nil, err
}
}
return secret, nil
}
func read(path string) (b []byte, err error) { return ioutil.ReadFile(path) }
...@@ -45,7 +45,7 @@ func TestDeploymentManifest(t *testing.T) { ...@@ -45,7 +45,7 @@ func TestDeploymentManifest(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
o, err := DeploymentManifest(api.NamespaceDefault, tt.image, tt.canary) o, err := DeploymentManifest(&Options{Namespace: api.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary})
if err != nil { if err != nil {
t.Fatalf("%s: error %q", tt.name, err) t.Fatalf("%s: error %q", tt.name, err)
} }
...@@ -146,7 +146,11 @@ func TestInstall_canary(t *testing.T) { ...@@ -146,7 +146,11 @@ func TestInstall_canary(t *testing.T) {
func TestUpgrade(t *testing.T) { func TestUpgrade(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0" image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false) existingDeployment := deployment(&Options{
Namespace: api.NamespaceDefault,
ImageSpec: "imageToReplace",
UseCanary: false,
})
existingService := service(api.NamespaceDefault) existingService := service(api.NamespaceDefault)
fc := &fake.Clientset{} fc := &fake.Clientset{}
...@@ -178,7 +182,11 @@ func TestUpgrade(t *testing.T) { ...@@ -178,7 +182,11 @@ func TestUpgrade(t *testing.T) {
func TestUpgrade_serviceNotFound(t *testing.T) { func TestUpgrade_serviceNotFound(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0" image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false) existingDeployment := deployment(&Options{
Namespace: api.NamespaceDefault,
ImageSpec: "imageToReplace",
UseCanary: false,
})
fc := &fake.Clientset{} fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
......
...@@ -16,6 +16,13 @@ limitations under the License. ...@@ -16,6 +16,13 @@ limitations under the License.
package installer // import "k8s.io/helm/cmd/helm/installer" package installer // import "k8s.io/helm/cmd/helm/installer"
import (
"fmt"
"k8s.io/helm/pkg/version"
)
const defaultImage = "gcr.io/kubernetes-helm/tiller"
// Options control how to install tiller into a cluster, upgrade, and uninstall tiller from a cluster. // Options control how to install tiller into a cluster, upgrade, and uninstall tiller from a cluster.
type Options struct { type Options struct {
// EnableTLS instructs tiller to serve with TLS enabled. // EnableTLS instructs tiller to serve with TLS enabled.
...@@ -43,7 +50,7 @@ type Options struct { ...@@ -43,7 +50,7 @@ type Options struct {
// key tiller should use. // key tiller should use.
// //
// Required and valid if and only if EnableTLS or VerifyTLS is set. // Required and valid if and only if EnableTLS or VerifyTLS is set.
TLSKey string TLSKeyFile string
// TLSCertFile identifies the file containing the pem encoded TLS // TLSCertFile identifies the file containing the pem encoded TLS
// certificate tiller should use. // certificate tiller should use.
...@@ -57,3 +64,16 @@ type Options struct { ...@@ -57,3 +64,16 @@ type Options struct {
// Required and valid if and only if VerifyTLS is set. // Required and valid if and only if VerifyTLS is set.
TLSCaCertFile string TLSCaCertFile string
} }
func (opts *Options) selectImage() string {
switch {
case opts.UseCanary:
return defaultImage + ":canary"
case opts.ImageSpec == "":
return fmt.Sprintf("%s:%s", defaultImage, version.Version)
default:
return opts.ImageSpec
}
}
func (opts *Options) tls() bool { return opts.EnableTLS || opts.VerifyTLS }
...@@ -55,7 +55,11 @@ func (f *fakeReaperFactory) Reaper(mapping *meta.RESTMapping) (kubectl.Reaper, e ...@@ -55,7 +55,11 @@ func (f *fakeReaperFactory) Reaper(mapping *meta.RESTMapping) (kubectl.Reaper, e
func TestUninstall(t *testing.T) { func TestUninstall(t *testing.T) {
existingService := service(api.NamespaceDefault) existingService := service(api.NamespaceDefault)
existingDeployment := deployment(api.NamespaceDefault, "image", false) existingDeployment := deployment(&Options{
Namespace: api.NamespaceDefault,
ImageSpec: "image",
UseCanary: false,
})
fc := &fake.Clientset{} fc := &fake.Clientset{}
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
...@@ -92,7 +96,7 @@ func TestUninstall(t *testing.T) { ...@@ -92,7 +96,7 @@ func TestUninstall(t *testing.T) {
} }
func TestUninstall_serviceNotFound(t *testing.T) { func TestUninstall_serviceNotFound(t *testing.T) {
existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false) existingDeployment := deployment(&Options{Namespace: api.NamespaceDefault, ImageSpec: "imageToReplace", UseCanary: false})
fc := &fake.Clientset{} fc := &fake.Clientset{}
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment