mirror of
https://github.com/zalando/postgres-operator.git
synced 2026-10-04 20:44:00 +02:00
Code refactor
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
package cluster
|
||||
|
||||
// Postgres ThirdPartyResource object i.e. Spilo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
var patroniUsers = []string{"superuser", "replication", "admin"}
|
||||
|
||||
//TODO: remove struct duplication
|
||||
type Config struct {
|
||||
Namespace string
|
||||
KubeClient *kubernetes.Clientset //TODO: move clients to the better place?
|
||||
RestClient *rest.RESTClient
|
||||
}
|
||||
|
||||
type Cluster struct {
|
||||
logger *logrus.Entry
|
||||
config Config
|
||||
etcdHost string
|
||||
dockerImage string
|
||||
cluster *spec.Postgresql
|
||||
pgUsers []pgUser
|
||||
}
|
||||
|
||||
type pgUser struct {
|
||||
username string
|
||||
secretKey string
|
||||
password []byte
|
||||
flags []string
|
||||
}
|
||||
|
||||
func New(cfg Config, spec *spec.Postgresql) *Cluster {
|
||||
lg := logrus.WithField("pkg", "cluster").WithField("cluster-name", spec.Metadata.Name)
|
||||
|
||||
//TODO: check if image exist
|
||||
dockerImage := fmt.Sprintf("registry.opensource.zalan.do/acid/spilo-%s", (*spec.Spec).PostgresqlParam.Version)
|
||||
|
||||
cluster := &Cluster{
|
||||
config: cfg,
|
||||
cluster: spec,
|
||||
logger: lg,
|
||||
etcdHost: constants.EtcdHost,
|
||||
dockerImage: dockerImage,
|
||||
}
|
||||
cluster.init()
|
||||
|
||||
return cluster
|
||||
}
|
||||
|
||||
func secretUserKey(userName string) string {
|
||||
return fmt.Sprintf("%s-password", userName)
|
||||
}
|
||||
|
||||
func (c *Cluster) init() {
|
||||
for _, userName := range patroniUsers {
|
||||
user := pgUser{
|
||||
username: userName,
|
||||
secretKey: secretUserKey(userName),
|
||||
password: util.RandomPasswordBytes(constants.PasswordLength),
|
||||
}
|
||||
c.pgUsers = append(c.pgUsers, user)
|
||||
}
|
||||
|
||||
for userName, userFlags := range (*c.cluster.Spec).Users {
|
||||
user := pgUser{
|
||||
username: userName,
|
||||
secretKey: secretUserKey(userName),
|
||||
password: util.RandomPasswordBytes(constants.PasswordLength),
|
||||
flags: userFlags,
|
||||
}
|
||||
c.pgUsers = append(c.pgUsers, user)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cluster) Create() error {
|
||||
c.createEndPoint()
|
||||
c.createService()
|
||||
c.applySecrets()
|
||||
c.createStatefulSet()
|
||||
|
||||
//TODO: wait for "spilo-role" label to appear on each pod
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cluster) Delete() error {
|
||||
clusterName := (*c.cluster).Metadata.Name
|
||||
nameSpace := c.config.Namespace
|
||||
orphanDependents := false
|
||||
deleteOptions := &v1.DeleteOptions{
|
||||
OrphanDependents: &orphanDependents,
|
||||
}
|
||||
|
||||
listOptions := v1.ListOptions{
|
||||
LabelSelector: fmt.Sprintf("%s=%s", "spilo-cluster", clusterName),
|
||||
}
|
||||
|
||||
kubeClient := c.config.KubeClient
|
||||
|
||||
podList, err := kubeClient.Pods(nameSpace).List(listOptions)
|
||||
if err != nil {
|
||||
c.logger.Errorf("Error: %+v", err)
|
||||
}
|
||||
|
||||
err = kubeClient.StatefulSets(nameSpace).Delete(clusterName, deleteOptions)
|
||||
if err != nil {
|
||||
c.logger.Errorf("Error: %+v", err)
|
||||
}
|
||||
c.logger.Infof("StatefulSet %s.%s has been deleted\n", nameSpace, clusterName)
|
||||
|
||||
for _, pod := range podList.Items {
|
||||
err = kubeClient.Pods(nameSpace).Delete(pod.Name, deleteOptions)
|
||||
if err != nil {
|
||||
c.logger.Errorf("Error while deleting Pod %s: %+v", pod.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
c.logger.Infof("Pod %s.%s has been deleted\n", pod.Namespace, pod.Name)
|
||||
}
|
||||
|
||||
serviceList, err := kubeClient.Services(nameSpace).List(listOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, service := range serviceList.Items {
|
||||
err = kubeClient.Services(nameSpace).Delete(service.Name, deleteOptions)
|
||||
if err != nil {
|
||||
c.logger.Errorf("Error while deleting Service %s: %+v", service.Name, err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
c.logger.Infof("Service %s.%s has been deleted\n", service.Namespace, service.Name)
|
||||
}
|
||||
|
||||
err = kubeClient.Secrets(nameSpace).Delete(clusterName, deleteOptions)
|
||||
if err != nil {
|
||||
c.logger.Errorf("Error while deleting Secret %s: %+v", clusterName, err)
|
||||
}
|
||||
|
||||
//TODO: delete key from etcd
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"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/util/k8sutil"
|
||||
)
|
||||
|
||||
func (c *Cluster) createStatefulSet() {
|
||||
clusterName := (*c.cluster).Metadata.Name
|
||||
|
||||
envVars := []v1.EnvVar{
|
||||
{
|
||||
Name: "SCOPE",
|
||||
Value: clusterName,
|
||||
},
|
||||
{
|
||||
Name: "PGROOT",
|
||||
Value: "/home/postgres/pgdata/pgroot",
|
||||
},
|
||||
{
|
||||
Name: "ETCD_HOST",
|
||||
Value: c.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: clusterName,
|
||||
},
|
||||
Key: secretUserKey("superuser"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "PGPASSWORD_ADMIN",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: clusterName,
|
||||
},
|
||||
Key: secretUserKey("admin"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "PGPASSWORD_STANDBY",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: clusterName,
|
||||
},
|
||||
Key: secretUserKey("replication"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resourceList := v1.ResourceList{}
|
||||
|
||||
if cpu := (*c.cluster).Spec.Resources.Cpu; cpu != "" {
|
||||
resourceList[v1.ResourceCPU] = resource.MustParse(cpu)
|
||||
}
|
||||
|
||||
if memory := (*c.cluster).Spec.Resources.Memory; memory != "" {
|
||||
resourceList[v1.ResourceMemory] = resource.MustParse(memory)
|
||||
}
|
||||
|
||||
container := v1.Container{
|
||||
Name: clusterName,
|
||||
Image: c.dockerImage,
|
||||
ImagePullPolicy: v1.PullAlways,
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: resourceList,
|
||||
},
|
||||
Ports: []v1.ContainerPort{
|
||||
{
|
||||
ContainerPort: 8008,
|
||||
Protocol: v1.ProtocolTCP,
|
||||
},
|
||||
{
|
||||
ContainerPort: 5432,
|
||||
Protocol: v1.ProtocolTCP,
|
||||
},
|
||||
},
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{
|
||||
Name: "pgdata",
|
||||
MountPath: "/home/postgres/pgdata", //TODO: fetch from manifesto
|
||||
},
|
||||
},
|
||||
Env: envVars,
|
||||
}
|
||||
|
||||
terminateGracePeriodSeconds := int64(30)
|
||||
|
||||
podSpec := v1.PodSpec{
|
||||
TerminationGracePeriodSeconds: &terminateGracePeriodSeconds,
|
||||
Volumes: []v1.Volume{
|
||||
{
|
||||
Name: "pgdata",
|
||||
VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}},
|
||||
},
|
||||
},
|
||||
Containers: []v1.Container{container},
|
||||
}
|
||||
|
||||
template := v1.PodTemplateSpec{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"application": "spilo",
|
||||
"spilo-cluster": clusterName,
|
||||
},
|
||||
Annotations: map[string]string{"pod.alpha.kubernetes.io/initialized": "true"},
|
||||
},
|
||||
Spec: podSpec,
|
||||
}
|
||||
|
||||
statefulSet := &v1beta1.StatefulSet{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: clusterName,
|
||||
Labels: map[string]string{
|
||||
"application": "spilo",
|
||||
"spilo-cluster": clusterName,
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.StatefulSetSpec{
|
||||
Replicas: &c.cluster.Spec.NumberOfInstances,
|
||||
ServiceName: clusterName,
|
||||
Template: template,
|
||||
},
|
||||
}
|
||||
|
||||
c.config.KubeClient.StatefulSets(c.config.Namespace).Create(statefulSet)
|
||||
}
|
||||
|
||||
func (c *Cluster) applySecrets() {
|
||||
clusterName := (*c.cluster).Metadata.Name
|
||||
secrets := make(map[string][]byte, len(c.pgUsers))
|
||||
for _, user := range c.pgUsers {
|
||||
secrets[user.secretKey] = user.password
|
||||
}
|
||||
|
||||
secret := v1.Secret{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: clusterName,
|
||||
Labels: map[string]string{
|
||||
"application": "spilo",
|
||||
"spilo-cluster": clusterName,
|
||||
},
|
||||
},
|
||||
Type: v1.SecretTypeOpaque,
|
||||
Data: secrets,
|
||||
}
|
||||
|
||||
_, err := c.config.KubeClient.Secrets(c.config.Namespace).Get(clusterName)
|
||||
|
||||
//TODO: possible race condition (as well as while creating the other objects)
|
||||
if !k8sutil.ResourceNotFound(err) {
|
||||
_, err = c.config.KubeClient.Secrets(c.config.Namespace).Update(&secret)
|
||||
} else {
|
||||
_, err = c.config.KubeClient.Secrets(c.config.Namespace).Create(&secret)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.logger.Errorf("Error while creating or updating secret: %+v", err)
|
||||
} else {
|
||||
c.logger.Infof("Secret created: %+v", secret)
|
||||
}
|
||||
|
||||
//TODO: remove secrets of the deleted users
|
||||
}
|
||||
|
||||
func (c *Cluster) createService() {
|
||||
clusterName := (*c.cluster).Metadata.Name
|
||||
|
||||
_, err := c.config.KubeClient.Services(c.config.Namespace).Get(clusterName)
|
||||
if !k8sutil.ResourceNotFound(err) {
|
||||
c.logger.Infof("Service '%s' already exists", clusterName)
|
||||
return
|
||||
}
|
||||
|
||||
service := v1.Service{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: clusterName,
|
||||
Labels: map[string]string{
|
||||
"application": "spilo",
|
||||
"spilo-cluster": clusterName,
|
||||
},
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Type: v1.ServiceTypeClusterIP,
|
||||
Ports: []v1.ServicePort{{Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = c.config.KubeClient.Services(c.config.Namespace).Create(&service)
|
||||
if err != nil {
|
||||
c.logger.Errorf("Error while creating service: %+v", err)
|
||||
} else {
|
||||
c.logger.Infof("Service created: %+v", service)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cluster) createEndPoint() {
|
||||
clusterName := (*c.cluster).Metadata.Name
|
||||
|
||||
_, err := c.config.KubeClient.Endpoints(c.config.Namespace).Get(clusterName)
|
||||
if !k8sutil.ResourceNotFound(err) {
|
||||
c.logger.Infof("Endpoint '%s' already exists", clusterName)
|
||||
return
|
||||
}
|
||||
|
||||
endPoint := v1.Endpoints{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: clusterName,
|
||||
Labels: map[string]string{
|
||||
"application": "spilo",
|
||||
"spilo-cluster": clusterName,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = c.config.KubeClient.Endpoints(c.config.Namespace).Create(&endPoint)
|
||||
if err != nil {
|
||||
c.logger.Errorf("Error while creating endpoint: %+v", err)
|
||||
} else {
|
||||
c.logger.Infof("Endpoint created: %+v", endPoint)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user