Allow configuring the operator via the YAML manifest. (#326)

* Up until now, the operator read its own configuration from the
configmap.  That has a number of limitations, i.e. when the
configuration value is not a scalar, but a map or a list. We use a
custom code based on github.com/kelseyhightower/envconfig to decode
non-scalar values out of plain text keys, but that breaks when the data
inside the keys contains both YAML-special elememtns (i.e. commas) and
complex quotes, one good example for that is search_path inside
`team_api_role_configuration`. In addition, reliance on the configmap
forced a flag structure on the configuration, making it hard to write
and to read (see
https://github.com/zalando-incubator/postgres-operator/pull/308#issuecomment-395131778).

The changes allow to supply the operator configuration in a proper YAML
file. That required registering a custom CRD to support the operator
configuration and provide an example at
manifests/postgresql-operator-default-configuration.yaml. At the moment,
both old configmap and the new CRD configuration is supported, so no
compatibility issues, however, in the future I'd like to deprecate the
configmap-based configuration altogether. Contrary to the
configmap-based configuration, the CRD one doesn't embed defaults into
the operator code, however, one can use the
manifests/postgresql-operator-default-configuration.yaml as a starting
point in order to build a custom configuration.

Since previously `ReadyWaitInterval` and `ReadyWaitTimeout` parameters
used to create the CRD were taken from the operator configuration, which
is not possible if the configuration itself is stored in the CRD object,
I've added the ability to specify them as environment variables
`CRD_READY_WAIT_INTERVAL` and `CRD_READY_WAIT_TIMEOUT` respectively.

Per review by @zerg-junior  and  @Jan-M.
This commit is contained in:
Oleksii Kliukin
2018-07-16 16:20:46 +02:00
committed by GitHub
parent e90a01050c
commit 3a9378d3b8
14 changed files with 584 additions and 45 deletions
+32 -17
View File
@@ -101,23 +101,24 @@ func (c *Controller) initOperatorConfig() {
c.logger.Infoln("no ConfigMap specified. Loading default values")
}
configMapData["watched_namespace"] = c.getEffectiveNamespace(os.Getenv("WATCHED_NAMESPACE"), configMapData["watched_namespace"])
if c.config.NoDatabaseAccess {
configMapData["enable_database_access"] = "false"
}
if c.config.NoTeamsAPI {
configMapData["enable_teams_api"] = "false"
}
c.opConfig = config.NewFromMap(configMapData)
c.warnOnDeprecatedOperatorParameters()
}
func (c *Controller) modifyConfigFromEnvironment() {
c.opConfig.WatchedNamespace = c.getEffectiveNamespace(os.Getenv("WATCHED_NAMESPACE"), c.opConfig.WatchedNamespace)
if c.config.NoDatabaseAccess {
c.opConfig.EnableDBAccess = c.config.NoDatabaseAccess
}
if c.config.NoTeamsAPI {
c.opConfig.EnableTeamsAPI = c.config.NoTeamsAPI
}
scalyrAPIKey := os.Getenv("SCALYR_API_KEY")
if scalyrAPIKey != "" {
c.opConfig.ScalyrAPIKey = scalyrAPIKey
}
}
// warningOnDeprecatedParameters emits warnings upon finding deprecated parmaters
@@ -163,20 +164,34 @@ func (c *Controller) initPodServiceAccount() {
func (c *Controller) initController() {
c.initClients()
c.initOperatorConfig()
if configObjectName := os.Getenv("POSTGRES_OPERATOR_CONFIGURATION_OBJECT"); configObjectName != "" {
if err := c.createConfigurationCRD(); err != nil {
c.logger.Fatalf("could not register Operator Configuration CustomResourceDefinition: %v", err)
}
if cfg, err := c.readOperatorConfigurationFromCRD(spec.GetOperatorNamespace(), configObjectName); err != nil {
c.logger.Fatalf("unable to read operator configuration: %v", err)
} else {
c.opConfig = c.importConfigurationFromCRD(&cfg.Configuration)
}
} else {
c.initOperatorConfig()
}
c.modifyConfigFromEnvironment()
if err := c.createPostgresCRD(); err != nil {
c.logger.Fatalf("could not register Postgres CustomResourceDefinition: %v", err)
}
c.initPodServiceAccount()
c.initSharedInformers()
c.logger.Infof("config: %s", c.opConfig.MustMarshal())
if c.opConfig.DebugLogging {
c.logger.Logger.Level = logrus.DebugLevel
}
if err := c.createCRD(); err != nil {
c.logger.Fatalf("could not register CustomResourceDefinition: %v", err)
}
c.logger.Infof("config: %s", c.opConfig.MustMarshal())
if infraRoles, err := c.getInfrastructureRoles(&c.opConfig.InfrastructureRolesSecretName); err != nil {
c.logger.Warningf("could not get infrastructure roles: %v", err)
+108
View File
@@ -0,0 +1,108 @@
package controller
import (
"encoding/json"
"fmt"
"github.com/zalando-incubator/postgres-operator/pkg/util/config"
"github.com/zalando-incubator/postgres-operator/pkg/util/constants"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"time"
)
func (c *Controller) readOperatorConfigurationFromCRD(configObjectNamespace, configObjectName string) (*config.OperatorConfiguration, error) {
var (
opConfig config.OperatorConfiguration
)
req := c.KubeClient.CRDREST.Get().
Name(configObjectName).
Namespace(configObjectNamespace).
Resource(constants.OperatorConfigCRDResource).
VersionedParams(&metav1.ListOptions{ResourceVersion: "0"}, metav1.ParameterCodec)
data, err := req.DoRaw()
if err != nil {
return nil, fmt.Errorf("could not get operator configuration object %s: %v", configObjectName, err)
}
if err = json.Unmarshal(data, &opConfig); err != nil {
return nil, fmt.Errorf("could not unmarshal operator configuration object %s, %v", configObjectName, err)
}
return &opConfig, nil
}
// importConfigurationFromCRD is a transitional function that converts CRD configuration to the one based on the configmap
func (c *Controller) importConfigurationFromCRD(fromCRD *config.OperatorConfigurationData) *config.Config {
result := &config.Config{}
result.EtcdHost = fromCRD.EtcdHost
result.DockerImage = fromCRD.DockerImage
result.Workers = fromCRD.Workers
result.MinInstances = fromCRD.MinInstances
result.MaxInstances = fromCRD.MaxInstances
result.ResyncPeriod = time.Duration(fromCRD.ResyncPeriod)
result.Sidecars = fromCRD.Sidecars
result.SuperUsername = fromCRD.PostgresUsersConfiguration.SuperUsername
result.ReplicationUsername = fromCRD.PostgresUsersConfiguration.ReplicationUsername
result.PodServiceAccountName = fromCRD.Kubernetes.PodServiceAccountName
result.PodServiceAccountDefinition = fromCRD.Kubernetes.PodServiceAccountDefinition
result.PodTerminateGracePeriod = time.Duration(fromCRD.Kubernetes.PodTerminateGracePeriod)
result.WatchedNamespace = fromCRD.Kubernetes.WatchedNamespace
result.PDBNameFormat = fromCRD.Kubernetes.PDBNameFormat
result.SecretNameTemplate = fromCRD.Kubernetes.SecretNameTemplate
result.OAuthTokenSecretName = fromCRD.Kubernetes.OAuthTokenSecretName
result.InfrastructureRolesSecretName = fromCRD.Kubernetes.InfrastructureRolesSecretName
result.PodRoleLabel = fromCRD.Kubernetes.PodRoleLabel
result.ClusterLabels = fromCRD.Kubernetes.ClusterLabels
result.ClusterNameLabel = fromCRD.Kubernetes.ClusterNameLabel
result.NodeReadinessLabel = fromCRD.Kubernetes.NodeReadinessLabel
result.DefaultCPURequest = fromCRD.PostgresPodResources.DefaultCPURequest
result.DefaultMemoryRequest = fromCRD.PostgresPodResources.DefaultMemoryRequest
result.DefaultCPULimit = fromCRD.PostgresPodResources.DefaultCPULimit
result.DefaultMemoryLimit = fromCRD.PostgresPodResources.DefaultMemoryLimit
result.ResourceCheckInterval = time.Duration(fromCRD.Timeouts.ResourceCheckInterval)
result.ResourceCheckTimeout = time.Duration(fromCRD.Timeouts.ResourceCheckTimeout)
result.PodLabelWaitTimeout = time.Duration(fromCRD.Timeouts.PodLabelWaitTimeout)
result.PodDeletionWaitTimeout = time.Duration(fromCRD.Timeouts.PodDeletionWaitTimeout)
result.ReadyWaitInterval = time.Duration(fromCRD.Timeouts.ReadyWaitInterval)
result.ReadyWaitTimeout = time.Duration(fromCRD.Timeouts.ReadyWaitTimeout)
result.DbHostedZone = fromCRD.LoadBalancer.DbHostedZone
result.EnableMasterLoadBalancer = fromCRD.LoadBalancer.EnableMasterLoadBalancer
result.EnableReplicaLoadBalancer = fromCRD.LoadBalancer.EnableReplicaLoadBalancer
result.MasterDNSNameFormat = fromCRD.LoadBalancer.MasterDNSNameFormat
result.ReplicaDNSNameFormat = fromCRD.LoadBalancer.ReplicaDNSNameFormat
result.WALES3Bucket = fromCRD.AWSGCP.WALES3Bucket
result.AWSRegion = fromCRD.AWSGCP.AWSRegion
result.LogS3Bucket = fromCRD.AWSGCP.LogS3Bucket
result.KubeIAMRole = fromCRD.AWSGCP.KubeIAMRole
result.DebugLogging = fromCRD.OperatorDebug.DebugLogging
result.EnableDBAccess = fromCRD.OperatorDebug.EnableDBAccess
result.EnableTeamsAPI = fromCRD.TeamsAPI.EnableTeamsAPI
result.TeamsAPIUrl = fromCRD.TeamsAPI.TeamsAPIUrl
result.TeamAPIRoleConfiguration = fromCRD.TeamsAPI.TeamAPIRoleConfiguration
result.EnableTeamSuperuser = fromCRD.TeamsAPI.EnableTeamSuperuser
result.TeamAdminRole = fromCRD.TeamsAPI.TeamAdminRole
result.PamRoleName = fromCRD.TeamsAPI.PamRoleName
result.APIPort = fromCRD.LoggingRESTAPI.APIPort
result.RingLogLines = fromCRD.LoggingRESTAPI.RingLogLines
result.ClusterHistoryEntries = fromCRD.LoggingRESTAPI.ClusterHistoryEntries
result.ScalyrAPIKey = fromCRD.Scalyr.ScalyrAPIKey
result.ScalyrImage = fromCRD.Scalyr.ScalyrImage
result.ScalyrServerURL = fromCRD.Scalyr.ScalyrServerURL
result.ScalyrCPURequest = fromCRD.Scalyr.ScalyrCPURequest
result.ScalyrMemoryRequest = fromCRD.Scalyr.ScalyrMemoryRequest
result.ScalyrCPULimit = fromCRD.Scalyr.ScalyrCPULimit
result.ScalyrMemoryLimit = fromCRD.Scalyr.ScalyrMemoryLimit
return result
}
+2 -2
View File
@@ -48,7 +48,7 @@ func (c *Controller) clusterListFunc(options metav1.ListOptions) (runtime.Object
req := c.KubeClient.CRDREST.
Get().
Namespace(c.opConfig.WatchedNamespace).
Resource(constants.CRDResource).
Resource(constants.PostgresCRDResource).
VersionedParams(&options, metav1.ParameterCodec)
b, err := req.DoRaw()
@@ -117,7 +117,7 @@ func (c *Controller) clusterWatchFunc(options metav1.ListOptions) (watch.Interfa
r, err := c.KubeClient.CRDREST.
Get().
Namespace(c.opConfig.WatchedNamespace).
Resource(constants.CRDResource).
Resource(constants.PostgresCRDResource).
VersionedParams(&options, metav1.ParameterCodec).
FieldsSelectorParam(nil).
Stream()
+16 -8
View File
@@ -47,20 +47,20 @@ func (c *Controller) clusterWorkerID(clusterName spec.NamespacedName) uint32 {
return c.clusterWorkers[clusterName]
}
func (c *Controller) createCRD() error {
func (c *Controller) createOperatorCRD(plural, singular, short string) error {
crd := &apiextv1beta1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: constants.CRDResource + "." + constants.CRDGroup,
Name: plural + "." + constants.CRDGroup,
},
Spec: apiextv1beta1.CustomResourceDefinitionSpec{
Group: constants.CRDGroup,
Version: constants.CRDApiVersion,
Names: apiextv1beta1.CustomResourceDefinitionNames{
Plural: constants.CRDResource,
Singular: constants.CRDKind,
ShortNames: []string{constants.CRDShort},
Kind: constants.CRDKind,
ListKind: constants.CRDKind + "List",
Plural: plural,
Singular: singular,
ShortNames: []string{short},
Kind: singular,
ListKind: singular + "List",
},
Scope: apiextv1beta1.NamespaceScoped,
},
@@ -75,7 +75,7 @@ func (c *Controller) createCRD() error {
c.logger.Infof("customResourceDefinition %q has been registered", crd.Name)
}
return wait.Poll(c.opConfig.CRD.ReadyWaitInterval, c.opConfig.CRD.ReadyWaitTimeout, func() (bool, error) {
return wait.Poll(c.config.CRDReadyWaitInterval, c.config.CRDReadyWaitTimeout, func() (bool, error) {
c, err := c.KubeClient.CustomResourceDefinitions().Get(crd.Name, metav1.GetOptions{})
if err != nil {
return false, err
@@ -98,6 +98,14 @@ func (c *Controller) createCRD() error {
})
}
func (c *Controller) createPostgresCRD() error {
return c.createOperatorCRD(constants.PostgresCRDResource, constants.PostgresCRDKind, constants.PostgresCRDShort)
}
func (c *Controller) createConfigurationCRD() error {
return c.createOperatorCRD(constants.OperatorConfigCRDResource, constants.OperatorConfigCRDKind, constants.OperatorConfigCRDShort)
}
func readDecodedRole(s string) (*spec.PgUser, error) {
var result spec.PgUser
if err := yaml.Unmarshal([]byte(s), &result); err != nil {