mirror of
https://github.com/zalando/postgres-operator.git
synced 2026-09-30 11:21:28 +02:00
Code refactor
This commit is contained in:
+113
-280
@@ -2,329 +2,162 @@ package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/pkg/api"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
v1beta1extensions "k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
"k8s.io/client-go/pkg/fields"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/etcd"
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/cluster"
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/util/constants"
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/util/k8sutil"
|
||||
)
|
||||
|
||||
const (
|
||||
ACTION_DELETE = "delete"
|
||||
ACTION_UPDATE = "update"
|
||||
ACTION_ADD = "add"
|
||||
)
|
||||
|
||||
type podEvent struct {
|
||||
namespace string
|
||||
name string
|
||||
actionType string
|
||||
type Config struct {
|
||||
Namespace string
|
||||
KubeClient *kubernetes.Clientset
|
||||
RestClient *rest.RESTClient
|
||||
}
|
||||
|
||||
type podWatcher struct {
|
||||
podNamespace string
|
||||
podName string
|
||||
eventsChannel chan podEvent
|
||||
subscribe bool
|
||||
type Controller struct {
|
||||
Config
|
||||
|
||||
logger *logrus.Entry
|
||||
events chan *Event
|
||||
clusters map[string]*cluster.Cluster
|
||||
stopChMap map[string]chan struct{}
|
||||
waitCluster sync.WaitGroup
|
||||
postgresqlInformer cache.SharedIndexInformer
|
||||
}
|
||||
|
||||
type SpiloController struct {
|
||||
podEvents chan podEvent
|
||||
podWatchers chan podWatcher
|
||||
SpiloClient *rest.RESTClient
|
||||
Clientset *kubernetes.Clientset
|
||||
etcdApiClient *etcd.EtcdClient
|
||||
|
||||
spiloInformer cache.SharedIndexInformer
|
||||
podInformer cache.SharedIndexInformer
|
||||
type Event struct {
|
||||
Type string
|
||||
Object *spec.Postgresql
|
||||
}
|
||||
|
||||
func podsListWatch(client *kubernetes.Clientset) *cache.ListWatch {
|
||||
return cache.NewListWatchFromClient(client.CoreV1().RESTClient(), "pods", api.NamespaceAll, fields.Everything())
|
||||
}
|
||||
|
||||
func newController(spiloClient *rest.RESTClient, clientset *kubernetes.Clientset, etcdClient *etcd.EtcdClient) *SpiloController {
|
||||
spiloController := &SpiloController{
|
||||
SpiloClient: spiloClient,
|
||||
Clientset: clientset,
|
||||
etcdApiClient: etcdClient,
|
||||
}
|
||||
|
||||
spiloInformer := cache.NewSharedIndexInformer(
|
||||
cache.NewListWatchFromClient(spiloClient, "spilos", api.NamespaceAll, fields.Everything()),
|
||||
&spec.Spilo{},
|
||||
resyncPeriod,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
)
|
||||
|
||||
spiloInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: spiloController.spiloAdd,
|
||||
UpdateFunc: spiloController.spiloUpdate,
|
||||
DeleteFunc: spiloController.spiloDelete,
|
||||
})
|
||||
|
||||
podInformer := cache.NewSharedIndexInformer(
|
||||
podsListWatch(clientset),
|
||||
&v1.Pod{},
|
||||
resyncPeriod,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
)
|
||||
|
||||
podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: spiloController.podAdd,
|
||||
UpdateFunc: spiloController.podUpdate,
|
||||
DeleteFunc: spiloController.podDelete,
|
||||
})
|
||||
|
||||
spiloController.spiloInformer = spiloInformer
|
||||
spiloController.podInformer = podInformer
|
||||
|
||||
spiloController.podEvents = make(chan podEvent)
|
||||
|
||||
return spiloController
|
||||
}
|
||||
|
||||
func (d *SpiloController) podAdd(obj interface{}) {
|
||||
pod := obj.(*v1.Pod)
|
||||
d.podEvents <- podEvent{
|
||||
namespace: pod.Namespace,
|
||||
name: pod.Name,
|
||||
actionType: ACTION_ADD,
|
||||
func New(cfg *Config) *Controller {
|
||||
return &Controller{
|
||||
Config: *cfg,
|
||||
logger: logrus.WithField("pkg", "controller"),
|
||||
clusters: make(map[string]*cluster.Cluster),
|
||||
stopChMap: map[string]chan struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *SpiloController) podDelete(obj interface{}) {
|
||||
pod := obj.(*v1.Pod)
|
||||
d.podEvents <- podEvent{
|
||||
namespace: pod.Namespace,
|
||||
name: pod.Name,
|
||||
actionType: ACTION_DELETE,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *SpiloController) podUpdate(old, cur interface{}) {
|
||||
oldPod := old.(*v1.Pod)
|
||||
d.podEvents <- podEvent{
|
||||
namespace: oldPod.Namespace,
|
||||
name: oldPod.Name,
|
||||
actionType: ACTION_UPDATE,
|
||||
}
|
||||
}
|
||||
|
||||
func (z *SpiloController) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) {
|
||||
func (c *Controller) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
wg.Add(1)
|
||||
|
||||
if err := EnsureSpiloThirdPartyResource(z.Clientset); err != nil {
|
||||
log.Fatalf("Couldn't create ThirdPartyResource: %s", err)
|
||||
}
|
||||
c.initController()
|
||||
|
||||
go z.spiloInformer.Run(stopCh)
|
||||
go z.podInformer.Run(stopCh)
|
||||
go z.podWatcher(stopCh)
|
||||
go c.watchTpr(stopCh)
|
||||
go c.watchTprEvents(stopCh)
|
||||
|
||||
c.logger.Info("Started working in background")
|
||||
}
|
||||
|
||||
func (c *Controller) watchTpr(stopCh <-chan struct{}) {
|
||||
go c.postgresqlInformer.Run(stopCh)
|
||||
|
||||
<-stopCh
|
||||
}
|
||||
|
||||
func (z *SpiloController) spiloAdd(obj interface{}) {
|
||||
spilo := obj.(*spec.Spilo)
|
||||
func (c *Controller) watchTprEvents(stopCh <-chan struct{}) {
|
||||
//fmt.Println("Watching tpr events")
|
||||
|
||||
clusterName := (*spilo).Metadata.Name
|
||||
ns := (*spilo).Metadata.Namespace
|
||||
|
||||
//TODO: check if object already exists before creating
|
||||
z.CreateEndPoint(ns, clusterName)
|
||||
z.CreateService(ns, clusterName)
|
||||
z.CreateSecrets(ns, clusterName)
|
||||
z.CreateStatefulSet(spilo)
|
||||
<-stopCh
|
||||
}
|
||||
|
||||
func (z *SpiloController) spiloUpdate(old, cur interface{}) {
|
||||
oldSpilo := old.(*spec.Spilo)
|
||||
curSpilo := cur.(*spec.Spilo)
|
||||
|
||||
if oldSpilo.Spec.NumberOfInstances != curSpilo.Spec.NumberOfInstances {
|
||||
z.UpdateStatefulSet(curSpilo)
|
||||
func (c *Controller) createTPR() error {
|
||||
tpr := &v1beta1extensions.ThirdPartyResource{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s.%s", constants.TPRName, constants.TPRVendor),
|
||||
},
|
||||
Versions: []v1beta1extensions.APIVersion{
|
||||
{Name: constants.TPRApiVersion},
|
||||
},
|
||||
Description: constants.TPRDescription,
|
||||
}
|
||||
|
||||
if oldSpilo.Spec.DockerImage != curSpilo.Spec.DockerImage {
|
||||
log.Printf("Updating DockerImage: %s.%s",
|
||||
curSpilo.Metadata.Namespace,
|
||||
curSpilo.Metadata.Name)
|
||||
_, err := c.KubeClient.ExtensionsV1beta1().ThirdPartyResources().Create(tpr)
|
||||
|
||||
z.UpdateStatefulSetImage(curSpilo)
|
||||
}
|
||||
|
||||
log.Printf("Update spilo old: %+v\ncurrent: %+v", *oldSpilo, *curSpilo)
|
||||
}
|
||||
|
||||
func (z *SpiloController) spiloDelete(obj interface{}) {
|
||||
spilo := obj.(*spec.Spilo)
|
||||
|
||||
err := z.DeleteStatefulSet(spilo.Metadata.Namespace, spilo.Metadata.Name)
|
||||
if err != nil {
|
||||
log.Printf("Error while deleting stateful set: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (z *SpiloController) DeleteStatefulSet(ns, clusterName string) error {
|
||||
orphanDependents := false
|
||||
deleteOptions := v1.DeleteOptions{
|
||||
OrphanDependents: &orphanDependents,
|
||||
}
|
||||
|
||||
listOptions := v1.ListOptions{
|
||||
LabelSelector: fmt.Sprintf("%s=%s", "spilo-cluster", clusterName),
|
||||
}
|
||||
|
||||
podList, err := z.Clientset.Pods(ns).List(listOptions)
|
||||
if err != nil {
|
||||
log.Printf("Error: %+v", err)
|
||||
}
|
||||
|
||||
err = z.Clientset.StatefulSets(ns).Delete(clusterName, &deleteOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("StatefulSet %s.%s has been deleted\n", ns, clusterName)
|
||||
|
||||
for _, pod := range podList.Items {
|
||||
err = z.Clientset.Pods(pod.Namespace).Delete(pod.Name, &deleteOptions)
|
||||
if err != nil {
|
||||
log.Printf("Error while deleting Pod %s: %+v", pod.Name, err)
|
||||
if !k8sutil.IsKubernetesResourceAlreadyExistError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Pod %s.%s has been deleted\n", pod.Namespace, pod.Name)
|
||||
}
|
||||
|
||||
serviceList, err := z.Clientset.Services(ns).List(listOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, service := range serviceList.Items {
|
||||
err = z.Clientset.Services(service.Namespace).Delete(service.Name, &deleteOptions)
|
||||
if err != nil {
|
||||
log.Printf("Error while deleting Service %s: %+v", service.Name, err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Service %s.%s has been deleted\n", service.Namespace, service.Name)
|
||||
}
|
||||
|
||||
z.etcdApiClient.DeleteEtcdKey(clusterName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (z *SpiloController) UpdateStatefulSet(spilo *spec.Spilo) {
|
||||
ns := (*spilo).Metadata.Namespace
|
||||
|
||||
statefulSet := z.createSetFromSpilo(spilo)
|
||||
_, err := z.Clientset.StatefulSets(ns).Update(&statefulSet)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error while updating StatefulSet: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (z *SpiloController) UpdateStatefulSetImage(spilo *spec.Spilo) {
|
||||
ns := (*spilo).Metadata.Namespace
|
||||
|
||||
z.UpdateStatefulSet(spilo)
|
||||
|
||||
listOptions := v1.ListOptions{
|
||||
LabelSelector: fmt.Sprintf("%s=%s", "spilo-cluster", (*spilo).Metadata.Name),
|
||||
}
|
||||
|
||||
pods, err := z.Clientset.Pods(ns).List(listOptions)
|
||||
if err != nil {
|
||||
log.Printf("Error while getting pods: %s", err)
|
||||
}
|
||||
|
||||
orphanDependents := true
|
||||
deleteOptions := v1.DeleteOptions{
|
||||
OrphanDependents: &orphanDependents,
|
||||
}
|
||||
|
||||
var masterPodName string
|
||||
for _, pod := range pods.Items {
|
||||
log.Printf("Pod processing: %s", pod.Name)
|
||||
|
||||
role, ok := pod.Labels["spilo-role"]
|
||||
if ok == false {
|
||||
log.Println("No spilo-role label")
|
||||
continue
|
||||
}
|
||||
if role == "master" {
|
||||
masterPodName = pod.Name
|
||||
log.Printf("Skipping master: %s", masterPodName)
|
||||
continue
|
||||
}
|
||||
|
||||
err := z.Clientset.Pods(ns).Delete(pod.Name, &deleteOptions)
|
||||
if err != nil {
|
||||
log.Printf("Error while deleting Pod %s.%s: %s", pod.Namespace, pod.Name, err)
|
||||
} else {
|
||||
log.Printf("Pod deleted: %s.%s", pod.Namespace, pod.Name)
|
||||
c.logger.Info("ThirdPartyResource already registered")
|
||||
}
|
||||
|
||||
w1 := podWatcher{
|
||||
podNamespace: pod.Namespace,
|
||||
podName: pod.Name,
|
||||
eventsChannel: make(chan podEvent, 1),
|
||||
subscribe: true,
|
||||
}
|
||||
|
||||
log.Printf("Watching pod %s.%s being recreated", pod.Namespace, pod.Name)
|
||||
z.podWatchers <- w1
|
||||
for e := range w1.eventsChannel {
|
||||
if e.actionType == ACTION_ADD { break }
|
||||
}
|
||||
|
||||
log.Printf("Pod %s.%s has been recreated", pod.Namespace, pod.Name)
|
||||
}
|
||||
|
||||
//TODO: do manual failover
|
||||
err = z.Clientset.Pods(ns).Delete(masterPodName, &deleteOptions)
|
||||
restClient := c.RestClient
|
||||
|
||||
return k8sutil.WaitTPRReady(restClient, constants.TPRReadyWaitInterval, constants.TPRReadyWaitTimeout, c.Namespace)
|
||||
}
|
||||
|
||||
func (c *Controller) makeClusterConfig() cluster.Config {
|
||||
return cluster.Config{
|
||||
Namespace: c.Namespace,
|
||||
KubeClient: c.KubeClient,
|
||||
RestClient: c.RestClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) initController() {
|
||||
err := c.createTPR()
|
||||
if err != nil {
|
||||
log.Printf("Error while deleting Pod %s.%s: %s", ns, masterPodName, err)
|
||||
} else {
|
||||
log.Printf("Pod deleted: %s.%s", ns, masterPodName)
|
||||
c.logger.Fatalf("Can't register ThirdPartyResource: %s", err)
|
||||
}
|
||||
|
||||
c.postgresqlInformer = cache.NewSharedIndexInformer(
|
||||
cache.NewListWatchFromClient(c.RestClient, constants.ResourceName, v1.NamespaceAll, fields.Everything()),
|
||||
&spec.Postgresql{},
|
||||
constants.ResyncPeriod,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
|
||||
|
||||
c.postgresqlInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: c.clusterAdd,
|
||||
UpdateFunc: c.clusterUpdate,
|
||||
DeleteFunc: c.clusterDelete,
|
||||
})
|
||||
}
|
||||
|
||||
func (z *SpiloController) podWatcher(stopCh <-chan struct{}) {
|
||||
//TODO: mind the namespace of the pod
|
||||
func (c *Controller) clusterAdd(obj interface{}) {
|
||||
pg := obj.(*spec.Postgresql)
|
||||
|
||||
watchers := make(map[string] podWatcher)
|
||||
for {
|
||||
select {
|
||||
case watcher := <-z.podWatchers:
|
||||
if watcher.subscribe {
|
||||
watchers[watcher.podName] = watcher
|
||||
} else {
|
||||
close(watcher.eventsChannel)
|
||||
delete(watchers, watcher.podName)
|
||||
}
|
||||
case event := <-z.podEvents:
|
||||
log.Printf("Pod watcher event: %s.%s - %s", event.namespace, event.name, event.actionType)
|
||||
log.Printf("Current watchers: %+v", watchers)
|
||||
podWatcher, ok := watchers[event.name]
|
||||
if ok == false {
|
||||
continue
|
||||
}
|
||||
|
||||
podWatcher.eventsChannel <- event
|
||||
}
|
||||
if pg.Spec == nil {
|
||||
return
|
||||
}
|
||||
|
||||
cluster := cluster.New(c.makeClusterConfig(), pg)
|
||||
cluster.Create()
|
||||
|
||||
c.logger.Infof("Add: %+v", cluster)
|
||||
}
|
||||
|
||||
func (c *Controller) clusterUpdate(prev, cur interface{}) {
|
||||
pgPrev := prev.(*spec.Postgresql)
|
||||
pgCur := cur.(*spec.Postgresql)
|
||||
|
||||
if pgPrev.Spec == nil || pgCur.Spec == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.logger.Infof("Update: %+v -> %+v", *pgPrev.Spec, *pgCur.Spec)
|
||||
}
|
||||
|
||||
func (c *Controller) clusterDelete(obj interface{}) {
|
||||
pg := obj.(*spec.Postgresql)
|
||||
if pg.Spec == nil {
|
||||
return
|
||||
}
|
||||
|
||||
cluster := cluster.New(c.makeClusterConfig(), pg)
|
||||
cluster.Delete()
|
||||
|
||||
c.logger.Infof("Delete: %+v", *pg.Spec)
|
||||
}
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func (z *SpiloController) CreateStatefulSet(spilo *spec.Spilo) {
|
||||
ns := (*spilo).Metadata.Namespace
|
||||
|
||||
statefulSet := z.createSetFromSpilo(spilo)
|
||||
|
||||
_, err := z.Clientset.StatefulSets(ns).Create(&statefulSet)
|
||||
if err != nil {
|
||||
log.Printf("Petset error: %+v", err)
|
||||
} else {
|
||||
log.Printf("Petset created: %+v", statefulSet)
|
||||
}
|
||||
}
|
||||
|
||||
func (z *SpiloController) createSetFromSpilo(spilo *spec.Spilo) v1beta1.StatefulSet {
|
||||
clusterName := (*spilo).Metadata.Name
|
||||
|
||||
envVars := []v1.EnvVar{
|
||||
{
|
||||
Name: "SCOPE",
|
||||
Value: clusterName,
|
||||
},
|
||||
{
|
||||
Name: "PGROOT",
|
||||
Value: "/home/postgres/pgdata/pgroot",
|
||||
},
|
||||
{
|
||||
Name: "ETCD_HOST",
|
||||
Value: spilo.Spec.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: "superuser-password",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "PGPASSWORD_ADMIN",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: clusterName,
|
||||
},
|
||||
Key: "admin-password",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "PGPASSWORD_STANDBY",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: clusterName,
|
||||
},
|
||||
Key: "replication-password",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resourceList := v1.ResourceList{}
|
||||
|
||||
if (*spilo).Spec.ResourceCPU != "" {
|
||||
resourceList[v1.ResourceCPU] = resource.MustParse((*spilo).Spec.ResourceCPU)
|
||||
}
|
||||
|
||||
if (*spilo).Spec.ResourceMemory != "" {
|
||||
resourceList[v1.ResourceMemory] = resource.MustParse((*spilo).Spec.ResourceMemory)
|
||||
}
|
||||
|
||||
container := v1.Container{
|
||||
Name: clusterName,
|
||||
Image: spilo.Spec.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",
|
||||
},
|
||||
},
|
||||
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,
|
||||
}
|
||||
|
||||
return v1beta1.StatefulSet{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: clusterName,
|
||||
Labels: map[string]string{
|
||||
"application": "spilo",
|
||||
"spilo-cluster": clusterName,
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.StatefulSetSpec{
|
||||
Replicas: &spilo.Spec.NumberOfInstances,
|
||||
ServiceName: clusterName,
|
||||
Template: template,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (z *SpiloController) CreateSecrets(ns, name string) {
|
||||
secret := v1.Secret{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: name,
|
||||
Labels: map[string]string{
|
||||
"application": "spilo",
|
||||
"spilo-cluster": name,
|
||||
},
|
||||
},
|
||||
Type: v1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
"superuser-password": []byte("emFsYW5kbw=="),
|
||||
"replication-password": []byte("cmVwLXBhc3M="),
|
||||
"admin-password": []byte("YWRtaW4="),
|
||||
},
|
||||
}
|
||||
|
||||
_, err := z.Clientset.Secrets(ns).Create(&secret)
|
||||
if err != nil {
|
||||
log.Printf("Secret error: %+v", err)
|
||||
} else {
|
||||
log.Printf("Secret created: %+v", secret)
|
||||
}
|
||||
}
|
||||
|
||||
func (z *SpiloController) CreateService(ns, name string) {
|
||||
service := v1.Service{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: name,
|
||||
Labels: map[string]string{
|
||||
"application": "spilo",
|
||||
"spilo-cluster": name,
|
||||
},
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Type: v1.ServiceTypeClusterIP,
|
||||
Ports: []v1.ServicePort{{Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := z.Clientset.Services(ns).Create(&service)
|
||||
if err != nil {
|
||||
log.Printf("Service error: %+v", err)
|
||||
} else {
|
||||
log.Printf("Service created: %+v", service)
|
||||
}
|
||||
}
|
||||
|
||||
func (z *SpiloController) CreateEndPoint(ns, name string) {
|
||||
endPoint := v1.Endpoints{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: name,
|
||||
Labels: map[string]string{
|
||||
"application": "spilo",
|
||||
"spilo-cluster": name,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := z.Clientset.Endpoints(ns).Create(&endPoint)
|
||||
if err != nil {
|
||||
log.Printf("Endpoint error: %+v", err)
|
||||
} else {
|
||||
log.Printf("Endpoint created: %+v", endPoint)
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"net/http"
|
||||
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/pkg/api"
|
||||
apierrors "k8s.io/client-go/pkg/api/errors"
|
||||
"k8s.io/client-go/pkg/api/unversioned"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
"k8s.io/client-go/pkg/runtime"
|
||||
"k8s.io/client-go/pkg/runtime/serializer"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
|
||||
)
|
||||
|
||||
var (
|
||||
VENDOR = "acid.zalan.do"
|
||||
VERSION = "0.0.1.dev"
|
||||
resyncPeriod = 5 * time.Minute
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
KubeConfig string
|
||||
OutOfCluster bool
|
||||
}
|
||||
|
||||
func KubernetesConfig(options Options) (config *rest.Config) {
|
||||
var err error
|
||||
if options.OutOfCluster {
|
||||
/* out-of-cluster process */
|
||||
rules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
overrides := &clientcmd.ConfigOverrides{}
|
||||
rules.ExplicitPath = options.KubeConfig
|
||||
config, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides).ClientConfig()
|
||||
} else {
|
||||
/* in-cluster pod */
|
||||
config, err = rest.InClusterConfig()
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("Couldn't get Kubernetes default config: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func newKubernetesSpiloClient(c *rest.Config) (*rest.RESTClient, error) {
|
||||
c.APIPath = "/apis"
|
||||
c.GroupVersion = &unversioned.GroupVersion{
|
||||
Group: VENDOR,
|
||||
Version: "v1",
|
||||
}
|
||||
c.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: api.Codecs}
|
||||
|
||||
schemeBuilder := runtime.NewSchemeBuilder(
|
||||
func(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(
|
||||
*c.GroupVersion,
|
||||
&spec.Spilo{},
|
||||
&spec.SpiloList{},
|
||||
&api.ListOptions{},
|
||||
&api.DeleteOptions{},
|
||||
)
|
||||
return nil
|
||||
})
|
||||
schemeBuilder.AddToScheme(api.Scheme)
|
||||
|
||||
return rest.RESTClientFor(c)
|
||||
}
|
||||
|
||||
//TODO: Move to separate package
|
||||
func IsKubernetesResourceNotFoundError(err error) bool {
|
||||
se, ok := err.(*apierrors.StatusError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if se.Status().Code == http.StatusNotFound && se.Status().Reason == unversioned.StatusReasonNotFound {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func EnsureSpiloThirdPartyResource(client *kubernetes.Clientset) error {
|
||||
// The resource doesn't exist, so we create it.
|
||||
tpr := v1beta1.ThirdPartyResource{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: fmt.Sprintf("spilo.%s", VENDOR),
|
||||
},
|
||||
Description: "A specification of Spilo StatefulSets",
|
||||
Versions: []v1beta1.APIVersion{
|
||||
{Name: "v1"},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := client.ExtensionsV1beta1().ThirdPartyResources().Create(&tpr)
|
||||
|
||||
if IsKubernetesResourceNotFoundError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"net/url"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/etcd"
|
||||
)
|
||||
|
||||
type SpiloOperator struct {
|
||||
Options
|
||||
|
||||
ClientSet *kubernetes.Clientset
|
||||
Client *rest.RESTClient
|
||||
Controller *SpiloController
|
||||
EtcdClient *etcd.EtcdClient
|
||||
}
|
||||
|
||||
|
||||
func getEtcdServiceName(cls *kubernetes.Clientset, config *rest.Config, outOfCluster bool) (etcdServiceName string) {
|
||||
etcdService, _ := cls.Services("default").Get("etcd-client")
|
||||
if outOfCluster {
|
||||
ports := etcdService.Spec.Ports[0]
|
||||
if ports.NodePort == 0 {
|
||||
log.Fatal("Etcd port is not exposed\nHint: add NodePort to your Etcd service")
|
||||
}
|
||||
nodeurl, _ := url.Parse(config.Host)
|
||||
etcdServiceName = fmt.Sprintf("http://%s:%d", strings.Split(nodeurl.Host, ":")[0], ports.NodePort)
|
||||
} else {
|
||||
if len(etcdService.Spec.Ports) != 1 {
|
||||
log.Fatal("Can't find Etcd service named 'etcd-client'")
|
||||
}
|
||||
etcdServiceName = fmt.Sprintf("%s.%s.svc.cluster.local", etcdService.Name, etcdService.Namespace)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func New(options Options) *SpiloOperator {
|
||||
config := KubernetesConfig(options)
|
||||
|
||||
spiloClient, err := newKubernetesSpiloClient(config)
|
||||
if err != nil {
|
||||
log.Fatalf("Couldn't create Spilo client: %s", err)
|
||||
}
|
||||
|
||||
clientSet, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatalf("Couldn't create Kubernetes client: %s", err)
|
||||
}
|
||||
|
||||
etcdClient := etcd.NewEctdClient(getEtcdServiceName(clientSet, config, options.OutOfCluster))
|
||||
|
||||
operator := &SpiloOperator{
|
||||
Options: options,
|
||||
ClientSet: clientSet,
|
||||
Client: spiloClient,
|
||||
Controller: newController(spiloClient, clientSet, etcdClient),
|
||||
}
|
||||
|
||||
return operator
|
||||
}
|
||||
|
||||
func (o *SpiloOperator) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) {
|
||||
go o.Controller.Run(stopCh, wg)
|
||||
|
||||
log.Println("Started working in background")
|
||||
}
|
||||
Reference in New Issue
Block a user