diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index bfb8e35d7..0bcaf09f0 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -256,6 +256,11 @@ func (c *Cluster) Create() error { } c.logger.Infof("pod disruption budget %q has been successfully created", util.NameFromMeta(pdb.ObjectMeta)) + if err = c.syncPodServiceAccounts(); err != nil { + return fmt.Errorf("could not sync pod service accounts: %v", err) + } + c.logger.Infof("pod service accounts have been successfully synced") + if c.Statefulset != nil { return fmt.Errorf("statefulset already exists in the cluster") } diff --git a/pkg/cluster/sync.go b/pkg/cluster/sync.go index 5a77e658b..133ea50fc 100644 --- a/pkg/cluster/sync.go +++ b/pkg/cluster/sync.go @@ -44,6 +44,12 @@ func (c *Cluster) Sync(newSpec *spec.Postgresql) (err error) { return } + c.logger.Debugf("syncing service accounts") + if err = c.syncPodServiceAccounts(); err != nil { + err = fmt.Errorf("could not sync service accounts: %v", err) + return + } + c.logger.Debugf("syncing services") if err = c.syncServices(); err != nil { err = fmt.Errorf("could not sync services: %v", err) @@ -103,6 +109,34 @@ func (c *Cluster) syncServices() error { return nil } +/* + Ensures the service account required by StatefulSets to create pods exists in all namespaces watched by the operator. +*/ +func (c *Cluster) syncPodServiceAccounts() error { + + podServiceAccount := c.Config.OpConfig.PodServiceAccountName + c.setProcessName("syncing pod service account in the watched namespaces") + + _, err := c.KubeClient.ServiceAccounts(c.Namespace).Get(podServiceAccount, metav1.GetOptions{}) + + if err != nil { + c.logger.Warnf("the pod service account %q is absent from the namespace %q. Stateful sets in the namespace are unable to create pods.", podServiceAccount, c.Namespace) + + c.OpConfig.PodServiceAccount.SetNamespace(c.Namespace) + + _, err = c.KubeClient.ServiceAccounts(c.Namespace).Create(c.OpConfig.PodServiceAccount) + if err != nil { + c.logger.Warnf("cannot deploy the pod service account %q defined in the config map to the %q namespace: %v", podServiceAccount, c.Namespace, err) + } else { + c.logger.Infof("successfully deployed the pod service account %q to the %q namespace", podServiceAccount, c.Namespace) + } + } else { + c.logger.Infof("successfully found the service account %q used to create pods to the namespace %q", podServiceAccount, c.Namespace) + } + + return err +} + func (c *Cluster) syncService(role PostgresRole) error { c.setProcessName("syncing %s service", role) diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index d19da5b84..4c47c72b0 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -8,6 +8,7 @@ import ( "github.com/Sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/tools/cache" @@ -113,11 +114,30 @@ func (c *Controller) initOperatorConfig() { if scalyrAPIKey != "" { c.opConfig.ScalyrAPIKey = scalyrAPIKey } + +} + +func (c *Controller) initPodServiceAccount() { + // re-uses k8s internal parsing. See k8s client-go issue #193 for explanation + decode := scheme.Codecs.UniversalDeserializer().Decode + obj, groupVersionKind, err := decode([]byte(c.opConfig.PodServiceAccountDefinition), nil, nil) + + switch { + case err != nil: + panic(fmt.Errorf("Unable to parse pod service account definiton from the operator config map: %v", err)) + case groupVersionKind.Kind != "ServiceAccount": + panic(fmt.Errorf("pod service account definiton in the operator config map defines another type of resource: %v", groupVersionKind.Kind)) + default: + c.opConfig.PodServiceAccount = obj.(*v1.ServiceAccount) + } + + // actual service accounts are deployed lazily at the time of cluster creation or sync } func (c *Controller) initController() { c.initClients() c.initOperatorConfig() + c.initPodServiceAccount() c.initSharedInformers() diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 0e653ce0b..15a4738b4 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/zalando-incubator/postgres-operator/pkg/spec" + "k8s.io/client-go/pkg/api/v1" ) // CRD describes CustomResourceDefinition specific configuration parameters @@ -67,21 +68,25 @@ type Config struct { Resources Auth Scalyr - WatchedNamespace string `name:"watched_namespace"` // special values: "*" means 'watch all namespaces', the empty string "" means 'watch a namespace where operator is deployed to' - EtcdHost string `name:"etcd_host" default:"etcd-client.default.svc.cluster.local:2379"` - DockerImage string `name:"docker_image" default:"registry.opensource.zalan.do/acid/spiloprivate-9.6:1.2-p4"` - ServiceAccountName string `name:"service_account_name" default:"operator"` - DbHostedZone string `name:"db_hosted_zone" default:"db.example.com"` - EtcdScope string `name:"etcd_scope" default:"service"` - WALES3Bucket string `name:"wal_s3_bucket"` - KubeIAMRole string `name:"kube_iam_role"` - DebugLogging bool `name:"debug_logging" default:"true"` - EnableDBAccess bool `name:"enable_database_access" default:"true"` - EnableTeamsAPI bool `name:"enable_teams_api" default:"true"` - EnableTeamSuperuser bool `name:"enable_team_superuser" default:"false"` - TeamAdminRole string `name:"team_admin_role" default:"admin"` - EnableMasterLoadBalancer bool `name:"enable_master_load_balancer" default:"true"` - EnableReplicaLoadBalancer bool `name:"enable_replica_load_balancer" default:"false"` + PodServiceAccount *v1.ServiceAccount + WatchedNamespace string `name:"watched_namespace"` // special values: "*" means 'watch all namespaces', the empty string "" means 'watch a namespace where operator is deployed to' + EtcdHost string `name:"etcd_host" default:"etcd-client.default.svc.cluster.local:2379"` + DockerImage string `name:"docker_image" default:"registry.opensource.zalan.do/acid/spiloprivate-9.6:1.2-p4"` + // re-use one account for both Spilo pods and the operator; this grants extra privileges to pods + ServiceAccountName string `name:"service_account_name" default:"operator"` + PodServiceAccountName string `name:"pod_service_account_name" default:"operator"` + PodServiceAccountDefinition string `name:"pod_service_account_definition" default:"apiVersion: v1\nkind: ServiceAccount\nmetadata:\n name: operator\n"` + DbHostedZone string `name:"db_hosted_zone" default:"db.example.com"` + EtcdScope string `name:"etcd_scope" default:"service"` + WALES3Bucket string `name:"wal_s3_bucket"` + KubeIAMRole string `name:"kube_iam_role"` + DebugLogging bool `name:"debug_logging" default:"true"` + EnableDBAccess bool `name:"enable_database_access" default:"true"` + EnableTeamsAPI bool `name:"enable_teams_api" default:"true"` + EnableTeamSuperuser bool `name:"enable_team_superuser" default:"false"` + TeamAdminRole string `name:"team_admin_role" default:"admin"` + EnableMasterLoadBalancer bool `name:"enable_master_load_balancer" default:"true"` + EnableReplicaLoadBalancer bool `name:"enable_replica_load_balancer" default:"false"` // deprecated and kept for backward compatibility EnableLoadBalancer *bool `name:"enable_load_balancer" default:"true"` MasterDNSNameFormat stringTemplate `name:"master_dns_name_format" default:"{cluster}.{team}.{hostedzone}"`