github.com/sdbaiguanghe/helm@v2.16.7+incompatible/cmd/helm/installer/install.go (about) 1 /* 2 Copyright The Helm Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package installer // import "k8s.io/helm/cmd/helm/installer" 18 19 import ( 20 "fmt" 21 "io/ioutil" 22 "strings" 23 24 "github.com/Masterminds/semver" 25 "github.com/ghodss/yaml" 26 appsv1 "k8s.io/api/apps/v1" 27 "k8s.io/api/core/v1" 28 extensionsv1beta1 "k8s.io/api/extensions/v1beta1" 29 apierrors "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/util/intstr" 33 "k8s.io/client-go/kubernetes" 34 appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1" 35 corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 36 37 "k8s.io/helm/pkg/chartutil" 38 "k8s.io/helm/pkg/tiller/environment" 39 ) 40 41 // Install uses Kubernetes client to install Tiller. 42 // 43 // Returns an error if the command failed. 44 func Install(client kubernetes.Interface, opts *Options) error { 45 if err := createDeployment(client.AppsV1(), opts); err != nil { 46 return err 47 } 48 if err := createService(client.CoreV1(), opts.Namespace); err != nil { 49 return err 50 } 51 if opts.tls() { 52 if err := createSecret(client.CoreV1(), opts); err != nil { 53 return err 54 } 55 } 56 return nil 57 } 58 59 // Upgrade uses Kubernetes client to upgrade Tiller to current version. 60 // 61 // Returns an error if the command failed. 62 func Upgrade(client kubernetes.Interface, opts *Options) error { 63 appsobj, err := client.AppsV1().Deployments(opts.Namespace).Get(deploymentName, metav1.GetOptions{}) 64 if err == nil { 65 // Can happen in two cases: 66 // 1. helm init inserted an apps/v1 Deployment up front in Kubernetes 67 // 2. helm init inserted an extensions/v1beta1 Deployment against a K8s cluster already 68 // supporting apps/v1 Deployment. In such a case K8s is returning the apps/v1 object anyway.` 69 // (for the same reason "kubectl convert" is being deprecated) 70 return upgradeAppsTillerDeployment(client, opts, appsobj) 71 } 72 73 extensionsobj, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Get(deploymentName, metav1.GetOptions{}) 74 if err == nil { 75 // User performed helm init against older version of kubernetes (Previous to 1.9) 76 return upgradeExtensionsTillerDeployment(client, opts, extensionsobj) 77 } 78 79 return err 80 } 81 82 func upgradeAppsTillerDeployment(client kubernetes.Interface, opts *Options, obj *appsv1.Deployment) error { 83 // Update the PodTemplateSpec section of the deployment 84 if err := updatePodTemplate(&obj.Spec.Template.Spec, opts); err != nil { 85 return err 86 } 87 88 if _, err := client.AppsV1().Deployments(opts.Namespace).Update(obj); err != nil { 89 return err 90 } 91 92 // If the service does not exist that would mean we are upgrading from a Tiller version 93 // that didn't deploy the service, so install it. 94 _, err := client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{}) 95 if apierrors.IsNotFound(err) { 96 return createService(client.CoreV1(), opts.Namespace) 97 } 98 99 return err 100 } 101 102 func upgradeExtensionsTillerDeployment(client kubernetes.Interface, opts *Options, obj *extensionsv1beta1.Deployment) error { 103 // Update the PodTemplateSpec section of the deployment 104 if err := updatePodTemplate(&obj.Spec.Template.Spec, opts); err != nil { 105 return err 106 } 107 108 if _, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Update(obj); err != nil { 109 return err 110 } 111 112 // If the service does not exist that would mean we are upgrading from a Tiller version 113 // that didn't deploy the service, so install it. 114 _, err := client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{}) 115 if apierrors.IsNotFound(err) { 116 return createService(client.CoreV1(), opts.Namespace) 117 } 118 119 return err 120 } 121 122 func updatePodTemplate(podSpec *v1.PodSpec, opts *Options) error { 123 tillerImage := podSpec.Containers[0].Image 124 clientImage := opts.SelectImage() 125 126 if semverCompare(tillerImage, clientImage) == -1 && !opts.ForceUpgrade { 127 return fmt.Errorf("current Tiller version %s is newer than client version %s, use --force-upgrade to downgrade", tillerImage, clientImage) 128 } 129 podSpec.Containers[0].Image = clientImage 130 podSpec.Containers[0].ImagePullPolicy = opts.pullPolicy() 131 podSpec.ServiceAccountName = opts.ServiceAccount 132 133 return nil 134 } 135 136 // semverCompare returns whether the client's version is older, equal or newer than the given image's version. 137 func semverCompare(tillerImage, clientImage string) int { 138 tillerVersion, err := string2semver(tillerImage) 139 if err != nil { 140 // same thing with unparsable tiller versions (e.g. canary releases). 141 return 1 142 } 143 144 // clientVersion, err := semver.NewVersion(currentVersion) 145 clientVersion, err := string2semver(clientImage) 146 if err != nil { 147 // aaaaaand same thing with unparsable helm versions (e.g. canary releases). 148 return 1 149 } 150 151 return clientVersion.Compare(tillerVersion) 152 } 153 154 func string2semver(image string) (*semver.Version, error) { 155 split := strings.Split(image, ":") 156 if len(split) < 2 { 157 // If we don't know the version, we consider the client version newer. 158 return nil, fmt.Errorf("no repository in image %s", image) 159 } 160 return semver.NewVersion(split[1]) 161 } 162 163 // createDeployment creates the Tiller Deployment resource. 164 func createDeployment(client appsv1client.DeploymentsGetter, opts *Options) error { 165 obj, err := generateDeployment(opts) 166 if err != nil { 167 return err 168 } 169 _, err = client.Deployments(obj.Namespace).Create(obj) 170 return err 171 172 } 173 174 // Deployment gets a deployment object that can be used to generate a manifest 175 // as a string. This object should not be submitted directly to the Kubernetes 176 // api 177 func Deployment(opts *Options) (*appsv1.Deployment, error) { 178 dep, err := generateDeployment(opts) 179 if err != nil { 180 return nil, err 181 } 182 dep.TypeMeta = metav1.TypeMeta{ 183 Kind: "Deployment", 184 APIVersion: "apps/v1", 185 } 186 return dep, nil 187 } 188 189 // createService creates the Tiller service resource 190 func createService(client corev1.ServicesGetter, namespace string) error { 191 obj := generateService(namespace) 192 _, err := client.Services(obj.Namespace).Create(obj) 193 return err 194 } 195 196 // Service gets a service object that can be used to generate a manifest as a 197 // string. This object should not be submitted directly to the Kubernetes api 198 func Service(namespace string) *v1.Service { 199 svc := generateService(namespace) 200 svc.TypeMeta = metav1.TypeMeta{ 201 Kind: "Service", 202 APIVersion: "v1", 203 } 204 return svc 205 } 206 207 // TillerManifests gets the Deployment, Service, and Secret (if tls-enabled) manifests 208 func TillerManifests(opts *Options) ([]string, error) { 209 dep, err := Deployment(opts) 210 if err != nil { 211 return []string{}, err 212 } 213 214 svc := Service(opts.Namespace) 215 216 objs := []runtime.Object{dep, svc} 217 218 if opts.EnableTLS { 219 secret, err := Secret(opts) 220 if err != nil { 221 return []string{}, err 222 } 223 objs = append(objs, secret) 224 } 225 226 manifests := make([]string, len(objs)) 227 for i, obj := range objs { 228 o, err := yaml.Marshal(obj) 229 if err != nil { 230 return []string{}, err 231 } 232 manifests[i] = string(o) 233 } 234 235 return manifests, err 236 } 237 238 func generateLabels(labels map[string]string) map[string]string { 239 labels["app"] = "helm" 240 return labels 241 } 242 243 // parseNodeSelectorsInto parses a comma delimited list of key=values pairs into a map. 244 func parseNodeSelectorsInto(labels string, m map[string]string) error { 245 kv := strings.Split(labels, ",") 246 for _, v := range kv { 247 el := strings.Split(v, "=") 248 if len(el) == 2 { 249 m[el[0]] = el[1] 250 } else { 251 return fmt.Errorf("invalid nodeSelector label: %q", kv) 252 } 253 } 254 return nil 255 } 256 func generateDeployment(opts *Options) (*appsv1.Deployment, error) { 257 labels := generateLabels(map[string]string{"name": "tiller"}) 258 nodeSelectors := map[string]string{} 259 if len(opts.NodeSelectors) > 0 { 260 err := parseNodeSelectorsInto(opts.NodeSelectors, nodeSelectors) 261 if err != nil { 262 return nil, err 263 } 264 } 265 d := &appsv1.Deployment{ 266 ObjectMeta: metav1.ObjectMeta{ 267 Namespace: opts.Namespace, 268 Name: deploymentName, 269 Labels: labels, 270 }, 271 Spec: appsv1.DeploymentSpec{ 272 Replicas: opts.getReplicas(), 273 Selector: &metav1.LabelSelector{ 274 MatchLabels: labels, 275 }, 276 Template: v1.PodTemplateSpec{ 277 ObjectMeta: metav1.ObjectMeta{ 278 Labels: labels, 279 }, 280 Spec: v1.PodSpec{ 281 ServiceAccountName: opts.ServiceAccount, 282 AutomountServiceAccountToken: &opts.AutoMountServiceAccountToken, 283 Containers: []v1.Container{ 284 { 285 Name: "tiller", 286 Image: opts.SelectImage(), 287 ImagePullPolicy: opts.pullPolicy(), 288 Ports: []v1.ContainerPort{ 289 {ContainerPort: environment.DefaultTillerPort, Name: "tiller"}, 290 {ContainerPort: environment.DefaultTillerProbePort, Name: "http"}, 291 }, 292 Env: []v1.EnvVar{ 293 {Name: "TILLER_NAMESPACE", Value: opts.Namespace}, 294 {Name: "TILLER_HISTORY_MAX", Value: fmt.Sprintf("%d", opts.MaxHistory)}, 295 }, 296 LivenessProbe: &v1.Probe{ 297 Handler: v1.Handler{ 298 HTTPGet: &v1.HTTPGetAction{ 299 Path: "/liveness", 300 Port: intstr.FromInt(environment.DefaultTillerProbePort), 301 }, 302 }, 303 InitialDelaySeconds: 1, 304 TimeoutSeconds: 1, 305 }, 306 ReadinessProbe: &v1.Probe{ 307 Handler: v1.Handler{ 308 HTTPGet: &v1.HTTPGetAction{ 309 Path: "/readiness", 310 Port: intstr.FromInt(environment.DefaultTillerProbePort), 311 }, 312 }, 313 InitialDelaySeconds: 1, 314 TimeoutSeconds: 1, 315 }, 316 }, 317 }, 318 HostNetwork: opts.EnableHostNetwork, 319 NodeSelector: nodeSelectors, 320 }, 321 }, 322 }, 323 } 324 325 if opts.tls() { 326 const certsDir = "/etc/certs" 327 328 var tlsVerify, tlsEnable = "", "1" 329 if opts.VerifyTLS { 330 tlsVerify = "1" 331 } 332 333 // Mount secret to "/etc/certs" 334 d.Spec.Template.Spec.Containers[0].VolumeMounts = append(d.Spec.Template.Spec.Containers[0].VolumeMounts, v1.VolumeMount{ 335 Name: "tiller-certs", 336 ReadOnly: true, 337 MountPath: certsDir, 338 }) 339 // Add environment variable required for enabling TLS 340 d.Spec.Template.Spec.Containers[0].Env = append(d.Spec.Template.Spec.Containers[0].Env, []v1.EnvVar{ 341 {Name: "TILLER_TLS_VERIFY", Value: tlsVerify}, 342 {Name: "TILLER_TLS_ENABLE", Value: tlsEnable}, 343 {Name: "TILLER_TLS_CERTS", Value: certsDir}, 344 }...) 345 // Add secret volume to deployment 346 d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, v1.Volume{ 347 Name: "tiller-certs", 348 VolumeSource: v1.VolumeSource{ 349 Secret: &v1.SecretVolumeSource{ 350 SecretName: "tiller-secret", 351 }, 352 }, 353 }) 354 } 355 // if --override values were specified, ultimately convert values and deployment to maps, 356 // merge them and convert back to Deployment 357 if len(opts.Values) > 0 { 358 // base deployment struct 359 var dd appsv1.Deployment 360 // get YAML from original deployment 361 dy, err := yaml.Marshal(d) 362 if err != nil { 363 return nil, fmt.Errorf("Error marshalling base Tiller Deployment: %s", err) 364 } 365 // convert deployment YAML to values 366 dv, err := chartutil.ReadValues(dy) 367 if err != nil { 368 return nil, fmt.Errorf("Error converting Deployment manifest: %s ", err) 369 } 370 dm := dv.AsMap() 371 // merge --set values into our map 372 sm, err := opts.valuesMap(dm) 373 if err != nil { 374 return nil, fmt.Errorf("Error merging --set values into Deployment manifest") 375 } 376 finalY, err := yaml.Marshal(sm) 377 if err != nil { 378 return nil, fmt.Errorf("Error marshalling merged map to YAML: %s ", err) 379 } 380 // convert merged values back into deployment 381 err = yaml.Unmarshal(finalY, &dd) 382 if err != nil { 383 return nil, fmt.Errorf("Error unmarshalling Values to Deployment manifest: %s ", err) 384 } 385 d = &dd 386 } 387 388 return d, nil 389 } 390 391 func generateService(namespace string) *v1.Service { 392 labels := generateLabels(map[string]string{"name": "tiller"}) 393 s := &v1.Service{ 394 ObjectMeta: metav1.ObjectMeta{ 395 Namespace: namespace, 396 Name: serviceName, 397 Labels: labels, 398 }, 399 Spec: v1.ServiceSpec{ 400 Type: v1.ServiceTypeClusterIP, 401 Ports: []v1.ServicePort{ 402 { 403 Name: "tiller", 404 Port: environment.DefaultTillerPort, 405 TargetPort: intstr.FromString("tiller"), 406 }, 407 }, 408 Selector: labels, 409 }, 410 } 411 return s 412 } 413 414 // Secret gets a secret object that can be used to generate a manifest as a 415 // string. This object should not be submitted directly to the Kubernetes api 416 func Secret(opts *Options) (*v1.Secret, error) { 417 secret, err := generateSecret(opts) 418 if err != nil { 419 return nil, err 420 } 421 422 secret.TypeMeta = metav1.TypeMeta{ 423 Kind: "Secret", 424 APIVersion: "v1", 425 } 426 427 return secret, nil 428 } 429 430 // createSecret creates the Tiller secret resource. 431 func createSecret(client corev1.SecretsGetter, opts *Options) error { 432 o, err := generateSecret(opts) 433 if err != nil { 434 return err 435 } 436 _, err = client.Secrets(o.Namespace).Create(o) 437 return err 438 } 439 440 // generateSecret builds the secret object that hold Tiller secrets. 441 func generateSecret(opts *Options) (*v1.Secret, error) { 442 443 labels := generateLabels(map[string]string{"name": "tiller"}) 444 secret := &v1.Secret{ 445 Type: v1.SecretTypeOpaque, 446 Data: make(map[string][]byte), 447 ObjectMeta: metav1.ObjectMeta{ 448 Name: secretName, 449 Labels: labels, 450 Namespace: opts.Namespace, 451 }, 452 } 453 var err error 454 if secret.Data["tls.key"], err = read(opts.TLSKeyFile); err != nil { 455 return nil, err 456 } 457 if secret.Data["tls.crt"], err = read(opts.TLSCertFile); err != nil { 458 return nil, err 459 } 460 if opts.VerifyTLS { 461 if secret.Data["ca.crt"], err = read(opts.TLSCaCertFile); err != nil { 462 return nil, err 463 } 464 } 465 return secret, nil 466 } 467 468 func read(path string) (b []byte, err error) { return ioutil.ReadFile(path) }