func UpgradeDeployment(kubecli kubernetes.Interface, vr *api.VaultService, d *appsv1beta1.Deployment) error {
	mu := intstr.FromInt(int(vr.Spec.Nodes - 1))
	d.Spec.Strategy.RollingUpdate.MaxUnavailable = &mu
	d.Spec.Template.Spec.Containers[0].Image = vaultImage(vr.Spec)
	_, err := kubecli.AppsV1beta1().Deployments(d.Namespace).Update(d)
	if err != nil {
		return fmt.Errorf("failed to upgrade deployment to (%s): %v", vaultImage(vr.Spec), err)
	}
	return nil
}
UpgradeDeployment sets deployment spec to:
- roll forward version
- keep active Vault node available by setting `maxUnavailable=N-1` and `maxSurge=1`
package k8sutil
import (
	"fmt"
	"io/ioutil"
	"path/filepath"
	"time"
	api "github.com/coreos/vault-operator/pkg/apis/vault/v1alpha1"
	"github.com/coreos/vault-operator/pkg/util/vaultutil"
	etcdCRAPI "github.com/coreos/etcd-operator/pkg/apis/etcd/v1beta2"
	etcdCRClient "github.com/coreos/etcd-operator/pkg/generated/clientset/versioned"
	"github.com/coreos/etcd-operator/pkg/util/retryutil"
	vaultapi "github.com/hashicorp/vault/api"
	appsv1beta1 "k8s.io/api/apps/v1beta1"
	"k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/client-go/kubernetes"
)
var (
	VaultConfigPath = "/run/vault/config/vault.hcl"
	vaultTLSAssetVolume  = "vault-tls-secret"
	vaultConfigVolName   = "vault-config"
	evnVaultRedirectAddr = "VAULT_API_ADDR"
	evnVaultClusterAddr  = "VAULT_CLUSTER_ADDR"
)
const (
	VaultClientPort      = 8200
	vaultClusterPort     = 8201
	vaultClientPortName  = "vault-client"
	vaultClusterPortName = "vault-cluster"
	exporterStatsdPort = 9125
	exporterPromPort   = 9102
	exporterImage      = "prom/statsd-exporter:v0.5.0"
)
func EtcdClientTLSSecretName(vaultName string) string {
	return vaultName + "-etcd-client-tls"
}
func EtcdServerTLSSecretName(vaultName string) string {
	return vaultName + "-etcd-server-tls"
}
func EtcdPeerTLSSecretName(vaultName string) string {
	return vaultName + "-etcd-peer-tls"
}
func DeployEtcdCluster(etcdCRCli etcdCRClient.Interface, v *api.VaultService) error {
	size := 3
	etcdCluster := &etcdCRAPI.EtcdCluster{
		TypeMeta: metav1.TypeMeta{
			Kind:       etcdCRAPI.EtcdClusterResourceKind,
			APIVersion: etcdCRAPI.SchemeGroupVersion.String(),
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      EtcdNameForVault(v.Name),
			Namespace: v.Namespace,
			Labels:    LabelsForVault(v.Name),
		},
		Spec: etcdCRAPI.ClusterSpec{
			Size: size,
			TLS: &etcdCRAPI.TLSPolicy{
				Static: &etcdCRAPI.StaticTLS{
					Member: &etcdCRAPI.MemberSecret{
						PeerSecret:   EtcdPeerTLSSecretName(v.Name),
						ServerSecret: EtcdServerTLSSecretName(v.Name),
					},
					OperatorSecret: EtcdClientTLSSecretName(v.Name),
				},
			},
			Pod: &etcdCRAPI.PodPolicy{
				EtcdEnv: []v1.EnvVar{{
					Name:  "ETCD_AUTO_COMPACTION_RETENTION",
					Value: "1",
				}},
			},
		},
	}
	if v.Spec.Pod != nil {
		etcdCluster.Spec.Pod.Resources = v.Spec.Pod.Resources
	}
	AddOwnerRefToObject(etcdCluster, AsOwner(v))
	_, err := etcdCRCli.EtcdV1beta2().EtcdClusters(v.Namespace).Create(etcdCluster)
	if err != nil {
		if apierrors.IsAlreadyExists(err) {
			return nil
		}
		return fmt.Errorf("deploy etcd cluster failed: %v", err)
	}
	err = retryutil.Retry(10*time.Second, 10, func() (bool, error) {
		er, err := etcdCRCli.EtcdV1beta2().EtcdClusters(v.Namespace).Get(EtcdNameForVault(v.Name), metav1.GetOptions{})
		if err != nil {
			return false, err
		}
		if len(er.Status.Members.Ready) < size {
			return false, nil
		}
		return true, nil
	})
	if err != nil {
		return fmt.Errorf("deploy etcd cluster failed: %v", err)
	}
	return nil
}
func DeleteEtcdCluster(etcdCRCli etcdCRClient.Interface, v *api.VaultService) error {
	err := etcdCRCli.EtcdV1beta2().EtcdClusters(v.Namespace).Delete(EtcdNameForVault(v.Name), nil)
	if err != nil && !apierrors.IsNotFound(err) {
		return err
	}
	return nil
}
func vaultContainer(v *api.VaultService) v1.Container {
	return v1.Container{
		Name:  "vault",
		Image: fmt.Sprintf("%s:%s", v.Spec.BaseImage, v.Spec.Version),
		Command: []string{
			"/bin/vault",
			"server",
			"-config=" + VaultConfigPath,
		},
		Env: []v1.EnvVar{
			{
				Name:  evnVaultRedirectAddr,
				Value: VaultServiceURL(v.GetName(), v.GetNamespace(), VaultClientPort),
			},
			{
				Name:  evnVaultClusterAddr,
				Value: VaultServiceURL(v.GetName(), v.GetNamespace(), vaultClusterPort),
			},
		},
		VolumeMounts: []v1.VolumeMount{{
			Name:      vaultConfigVolName,
			MountPath: filepath.Dir(VaultConfigPath),
		}},
		SecurityContext: &v1.SecurityContext{
			Capabilities: &v1.Capabilities{
				Add: []v1.Capability{"IPC_LOCK"},
			},
		},
		Ports: []v1.ContainerPort{{
			Name:          vaultClientPortName,
			ContainerPort: int32(VaultClientPort),
		}, {
			Name:          vaultClusterPortName,
			ContainerPort: int32(vaultClusterPort),
		}},
		LivenessProbe: &v1.Probe{
			Handler: v1.Handler{
				Exec: &v1.ExecAction{
					Command: []string{
						"curl",
						"--connect-timeout", "5",
						"--max-time", "10",
						"-k", "-s",
						fmt.Sprintf("https://localhost:%d/v1/sys/health", VaultClientPort),
					},
				},
			},
			InitialDelaySeconds: 10,
			TimeoutSeconds:      10,
			PeriodSeconds:       60,
			FailureThreshold:    3,
		},
		ReadinessProbe: &v1.Probe{
			Handler: v1.Handler{
				HTTPGet: &v1.HTTPGetAction{
					Path:   "/v1/sys/health",
					Port:   intstr.FromInt(VaultClientPort),
					Scheme: v1.URISchemeHTTPS,
				},
			},
			InitialDelaySeconds: 10,
			TimeoutSeconds:      10,
			PeriodSeconds:       10,
			FailureThreshold:    3,
		},
	}
}
func statsdExporterContainer() v1.Container {
	return v1.Container{
		Name:  "statsd-exporter",
		Image: exporterImage,
		Ports: []v1.ContainerPort{{
			Name:          "statsd",
			ContainerPort: exporterStatsdPort,
			Protocol:      "UDP",
		}, {
			Name:          "prometheus",
			ContainerPort: exporterPromPort,
			Protocol:      "TCP",
		}},
	}
}
func DeployVault(kubecli kubernetes.Interface, v *api.VaultService) error {
	selector := LabelsForVault(v.GetName())
	podTempl := v1.PodTemplateSpec{
		ObjectMeta: metav1.ObjectMeta{
			Name:   v.GetName(),
			Labels: selector,
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{vaultContainer(v), statsdExporterContainer()},
			Volumes: []v1.Volume{{
				Name: vaultConfigVolName,
				VolumeSource: v1.VolumeSource{
					ConfigMap: &v1.ConfigMapVolumeSource{
						LocalObjectReference: v1.LocalObjectReference{
							Name: ConfigMapNameForVault(v),
						},
					},
				},
			}},
		},
	}
	if v.Spec.Pod != nil {
		applyPodPolicy(&podTempl.Spec, v.Spec.Pod)
	}
	configEtcdBackendTLS(&podTempl, v)
	configVaultServerTLS(&podTempl, v)
	d := &appsv1beta1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:   v.GetName(),
			Labels: selector,
		},
		Spec: appsv1beta1.DeploymentSpec{
			Replicas: &v.Spec.Nodes,
			Selector: &metav1.LabelSelector{MatchLabels: selector},
			Template: podTempl,
			Strategy: appsv1beta1.DeploymentStrategy{
				Type: appsv1beta1.RollingUpdateDeploymentStrategyType,
				RollingUpdate: &appsv1beta1.RollingUpdateDeployment{
					MaxUnavailable: func(a intstr.IntOrString) *intstr.IntOrString { return &a }(intstr.FromInt(1)),
					MaxSurge:       func(a intstr.IntOrString) *intstr.IntOrString { return &a }(intstr.FromInt(1)),
				},
			},
		},
	}
	AddOwnerRefToObject(d, AsOwner(v))
	_, err := kubecli.AppsV1beta1().Deployments(v.Namespace).Create(d)
	if err != nil && !apierrors.IsAlreadyExists(err) {
		return err
	}
	svc := &v1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:   v.Name,
			Labels: selector,
		},
		Spec: v1.ServiceSpec{
			Selector: selector,
			Ports: []v1.ServicePort{
				{
					Name:     vaultClientPortName,
					Protocol: v1.ProtocolTCP,
					Port:     VaultClientPort,
				},
				{
					Name:     vaultClusterPortName,
					Protocol: v1.ProtocolTCP,
					Port:     vaultClusterPort,
				},
				{
					Name:     "prometheus",
					Protocol: v1.ProtocolTCP,
					Port:     exporterPromPort,
				},
			},
		},
	}
	AddOwnerRefToObject(svc, AsOwner(v))
	_, err = kubecli.CoreV1().Services(v.Namespace).Create(svc)
	if err != nil && !apierrors.IsAlreadyExists(err) {
		return fmt.Errorf("failed to create vault service: %v", err)
	}
	return nil
}