271 lines
6.8 KiB
Go
271 lines
6.8 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"
|
|
"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,
|
|
Limits: v1.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: util.ClusterDNSName(c.Metadata.Name, c.TeamName(), c.OpConfig.DbHostedZone),
|
|
},
|
|
},
|
|
Spec: v1.ServiceSpec{
|
|
Type: v1.ServiceTypeLoadBalancer,
|
|
Ports: []v1.ServicePort{{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
|
|
}
|