269 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			269 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Go
		
	
	
	
package cluster
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
 | 
						|
	"k8s.io/client-go/pkg/api/resource"
 | 
						|
	"k8s.io/client-go/pkg/api/v1"
 | 
						|
	"k8s.io/client-go/pkg/apis/apps/v1beta1"
 | 
						|
	"k8s.io/client-go/pkg/util/intstr"
 | 
						|
 | 
						|
	"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
 | 
						|
	"github.bus.zalan.do/acid/postgres-operator/pkg/util/constants"
 | 
						|
)
 | 
						|
 | 
						|
func resourceList(resources spec.Resources) *v1.ResourceList {
 | 
						|
	resourceList := v1.ResourceList{}
 | 
						|
	if resources.Cpu != "" {
 | 
						|
		resourceList[v1.ResourceCPU] = resource.MustParse(resources.Cpu)
 | 
						|
	}
 | 
						|
 | 
						|
	if resources.Memory != "" {
 | 
						|
		resourceList[v1.ResourceMemory] = resource.MustParse(resources.Memory)
 | 
						|
	}
 | 
						|
 | 
						|
	return &resourceList
 | 
						|
}
 | 
						|
 | 
						|
func (c *Cluster) genPodTemplate(resourceList *v1.ResourceList, pgVersion string) *v1.PodTemplateSpec {
 | 
						|
	envVars := []v1.EnvVar{
 | 
						|
		{
 | 
						|
			Name:  "SCOPE",
 | 
						|
			Value: c.Metadata.Name,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Name:  "PGROOT",
 | 
						|
			Value: "/home/postgres/pgdata/pgroot",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Name:  "ETCD_HOST",
 | 
						|
			Value: c.OpConfig.EtcdHost,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Name: "POD_IP",
 | 
						|
			ValueFrom: &v1.EnvVarSource{
 | 
						|
				FieldRef: &v1.ObjectFieldSelector{
 | 
						|
					APIVersion: "v1",
 | 
						|
					FieldPath:  "status.podIP",
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Name: "POD_NAMESPACE",
 | 
						|
			ValueFrom: &v1.EnvVarSource{
 | 
						|
				FieldRef: &v1.ObjectFieldSelector{
 | 
						|
					APIVersion: "v1",
 | 
						|
					FieldPath:  "metadata.namespace",
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Name: "PGPASSWORD_SUPERUSER",
 | 
						|
			ValueFrom: &v1.EnvVarSource{
 | 
						|
				SecretKeyRef: &v1.SecretKeySelector{
 | 
						|
					LocalObjectReference: v1.LocalObjectReference{
 | 
						|
						Name: c.credentialSecretName(c.OpConfig.SuperUsername),
 | 
						|
					},
 | 
						|
					Key: "password",
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Name: "PGPASSWORD_STANDBY",
 | 
						|
			ValueFrom: &v1.EnvVarSource{
 | 
						|
				SecretKeyRef: &v1.SecretKeySelector{
 | 
						|
					LocalObjectReference: v1.LocalObjectReference{
 | 
						|
						Name: c.credentialSecretName(c.OpConfig.ReplicationUsername),
 | 
						|
					},
 | 
						|
					Key: "password",
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Name:  "PAM_OAUTH2",
 | 
						|
			Value: c.OpConfig.PamConfiguration,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Name: "SPILO_CONFIGURATION",
 | 
						|
			Value: fmt.Sprintf(`
 | 
						|
postgresql:
 | 
						|
  bin_dir: /usr/lib/postgresql/%s/bin
 | 
						|
bootstrap:
 | 
						|
  initdb:
 | 
						|
  - auth-host: md5
 | 
						|
  - auth-local: trust
 | 
						|
  users:
 | 
						|
    %s:
 | 
						|
      password: NULL
 | 
						|
      options:
 | 
						|
        - createdb
 | 
						|
        - nologin
 | 
						|
  pg_hba:
 | 
						|
  - hostnossl all all all reject
 | 
						|
  - hostssl   all +%s all pam
 | 
						|
  - hostssl   all all all md5`, pgVersion, c.OpConfig.PamRoleName, c.OpConfig.PamRoleName),
 | 
						|
		},
 | 
						|
	}
 | 
						|
	if c.OpConfig.WALES3Bucket != "" {
 | 
						|
		envVars = append(envVars, v1.EnvVar{Name: "WAL_S3_BUCKET", Value: c.OpConfig.WALES3Bucket})
 | 
						|
	}
 | 
						|
	privilegedMode := bool(true)
 | 
						|
	container := v1.Container{
 | 
						|
		Name:            c.Metadata.Name,
 | 
						|
		Image:           c.OpConfig.DockerImage,
 | 
						|
		ImagePullPolicy: v1.PullAlways,
 | 
						|
		Resources: v1.ResourceRequirements{
 | 
						|
			Requests: *resourceList,
 | 
						|
		},
 | 
						|
		Ports: []v1.ContainerPort{
 | 
						|
			{
 | 
						|
				ContainerPort: 8008,
 | 
						|
				Protocol:      v1.ProtocolTCP,
 | 
						|
			},
 | 
						|
			{
 | 
						|
				ContainerPort: 5432,
 | 
						|
				Protocol:      v1.ProtocolTCP,
 | 
						|
			},
 | 
						|
			{
 | 
						|
				ContainerPort: 8080,
 | 
						|
				Protocol:      v1.ProtocolTCP,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		VolumeMounts: []v1.VolumeMount{
 | 
						|
			{
 | 
						|
				Name:      constants.DataVolumeName,
 | 
						|
				MountPath: "/home/postgres/pgdata", //TODO: fetch from manifesto
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Env: envVars,
 | 
						|
		SecurityContext: &v1.SecurityContext{
 | 
						|
			Privileged: &privilegedMode,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	terminateGracePeriodSeconds := int64(30)
 | 
						|
 | 
						|
	podSpec := v1.PodSpec{
 | 
						|
		ServiceAccountName:            c.OpConfig.ServiceAccountName,
 | 
						|
		TerminationGracePeriodSeconds: &terminateGracePeriodSeconds,
 | 
						|
		Containers:                    []v1.Container{container},
 | 
						|
	}
 | 
						|
 | 
						|
	template := v1.PodTemplateSpec{
 | 
						|
		ObjectMeta: v1.ObjectMeta{
 | 
						|
			Labels:    c.labelsSet(),
 | 
						|
			Namespace: c.Metadata.Name,
 | 
						|
		},
 | 
						|
		Spec: podSpec,
 | 
						|
	}
 | 
						|
	if c.OpConfig.KubeIAMRole != "" {
 | 
						|
		template.Annotations = map[string]string{constants.KubeIAmAnnotation: c.OpConfig.KubeIAMRole}
 | 
						|
	}
 | 
						|
 | 
						|
	return &template
 | 
						|
}
 | 
						|
 | 
						|
func (c *Cluster) genStatefulSet(spec spec.PostgresSpec) *v1beta1.StatefulSet {
 | 
						|
	resourceList := resourceList(spec.Resources)
 | 
						|
	podTemplate := c.genPodTemplate(resourceList, spec.PgVersion)
 | 
						|
	volumeClaimTemplate := persistentVolumeClaimTemplate(spec.Volume.Size, spec.Volume.StorageClass)
 | 
						|
 | 
						|
	statefulSet := &v1beta1.StatefulSet{
 | 
						|
		ObjectMeta: v1.ObjectMeta{
 | 
						|
			Name:      c.Metadata.Name,
 | 
						|
			Namespace: c.Metadata.Namespace,
 | 
						|
			Labels:    c.labelsSet(),
 | 
						|
		},
 | 
						|
		Spec: v1beta1.StatefulSetSpec{
 | 
						|
			Replicas:             &spec.NumberOfInstances,
 | 
						|
			ServiceName:          c.Metadata.Name,
 | 
						|
			Template:             *podTemplate,
 | 
						|
			VolumeClaimTemplates: []v1.PersistentVolumeClaim{*volumeClaimTemplate},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	return statefulSet
 | 
						|
}
 | 
						|
 | 
						|
func persistentVolumeClaimTemplate(volumeSize, volumeStorageClass string) *v1.PersistentVolumeClaim {
 | 
						|
	metadata := v1.ObjectMeta{
 | 
						|
		Name: constants.DataVolumeName,
 | 
						|
	}
 | 
						|
	if volumeStorageClass != "" {
 | 
						|
		// TODO: check if storage class exists
 | 
						|
		metadata.Annotations = map[string]string{"volume.beta.kubernetes.io/storage-class": volumeStorageClass}
 | 
						|
	} else {
 | 
						|
		metadata.Annotations = map[string]string{"volume.alpha.kubernetes.io/storage-class": "default"}
 | 
						|
	}
 | 
						|
 | 
						|
	volumeClaim := &v1.PersistentVolumeClaim{
 | 
						|
		ObjectMeta: metadata,
 | 
						|
		Spec: v1.PersistentVolumeClaimSpec{
 | 
						|
			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
 | 
						|
			Resources: v1.ResourceRequirements{
 | 
						|
				Requests: v1.ResourceList{
 | 
						|
					v1.ResourceStorage: resource.MustParse(volumeSize),
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	return volumeClaim
 | 
						|
}
 | 
						|
 | 
						|
func (c *Cluster) genUserSecrets() (secrets map[string]*v1.Secret, err error) {
 | 
						|
	secrets = make(map[string]*v1.Secret, len(c.pgUsers))
 | 
						|
	namespace := c.Metadata.Namespace
 | 
						|
	for username, pgUser := range c.pgUsers {
 | 
						|
		//Skip users with no password i.e. human users (they'll be authenticated using pam)
 | 
						|
		if pgUser.Password == "" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		secret := v1.Secret{
 | 
						|
			ObjectMeta: v1.ObjectMeta{
 | 
						|
				Name:      c.credentialSecretName(username),
 | 
						|
				Namespace: namespace,
 | 
						|
				Labels:    c.labelsSet(),
 | 
						|
			},
 | 
						|
			Type: v1.SecretTypeOpaque,
 | 
						|
			Data: map[string][]byte{
 | 
						|
				"username": []byte(pgUser.Name),
 | 
						|
				"password": []byte(pgUser.Password),
 | 
						|
			},
 | 
						|
		}
 | 
						|
		secrets[username] = &secret
 | 
						|
	}
 | 
						|
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func (c *Cluster) genService(allowedSourceRanges []string) *v1.Service {
 | 
						|
	service := &v1.Service{
 | 
						|
		ObjectMeta: v1.ObjectMeta{
 | 
						|
			Name:      c.Metadata.Name,
 | 
						|
			Namespace: c.Metadata.Namespace,
 | 
						|
			Labels:    c.labelsSet(),
 | 
						|
			Annotations: map[string]string{
 | 
						|
				constants.ZalandoDnsNameAnnotation: c.dnsName(),
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Spec: v1.ServiceSpec{
 | 
						|
			Type:  v1.ServiceTypeLoadBalancer,
 | 
						|
			Ports: []v1.ServicePort{{Name: "postgresql", Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}}},
 | 
						|
			LoadBalancerSourceRanges: allowedSourceRanges,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	return service
 | 
						|
}
 | 
						|
 | 
						|
func (c *Cluster) genEndpoints() *v1.Endpoints {
 | 
						|
	endpoints := &v1.Endpoints{
 | 
						|
		ObjectMeta: v1.ObjectMeta{
 | 
						|
			Name:      c.Metadata.Name,
 | 
						|
			Namespace: c.Metadata.Namespace,
 | 
						|
			Labels:    c.labelsSet(),
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	return endpoints
 | 
						|
}
 |