Merge pull request #231 from zalando-incubator/default-to-watched-ns
Operator should default to the watched namespace
This commit is contained in:
commit
04557922c0
17
README.md
17
README.md
|
|
@ -61,6 +61,23 @@ to test your that your setup is working.
|
|||
|
||||
Note: if you use multiple Kubernetes clusters, you can switch to Minikube with `kubectl config use-context minikube`
|
||||
|
||||
### Select the namespace to deploy to
|
||||
|
||||
The operator can run in a namespace other than `default`. For example, to use the `test` namespace, run the following before deploying the operator's manifests:
|
||||
|
||||
kubectl create namespace test
|
||||
kubectl config set-context minikube --namespace=test
|
||||
|
||||
All subsequent `kubectl` commands will work with the `test` namespace. The operator will run in this namespace and look up needed resources - such as its config map - there.
|
||||
|
||||
### Specify the namespace to watch
|
||||
|
||||
Watching a namespace for an operator means tracking requests to change Postgresql clusters in the namespace such as "increase the number of Postgresql replicas to 5" and reacting to the requests, in this example by actually scaling up.
|
||||
|
||||
By default, the operator watches the namespace it is deployed to. You can change this by altering the `WATCHED_NAMESPACE` env var in the operator deployment manifest or the `watched_namespace` field in the operator configmap. In the case both are set, the env var takes the precedence.
|
||||
|
||||
Note that for an operator to manage pods in the watched namespace, the operator's service account (as specified in the operator deployment manifest) has to have appropriate privileges to access the watched namespace. The watched namespace also needs to have a (possibly different) service account in the case database pods need to talk to the Kubernetes API (e.g. when using Kubernetes-native configuration of Patroni).
|
||||
|
||||
### Create ConfigMap
|
||||
|
||||
ConfigMap is used to store the configuration of the operator
|
||||
|
|
|
|||
14
cmd/main.go
14
cmd/main.go
|
|
@ -29,11 +29,21 @@ func init() {
|
|||
|
||||
configMapRawName := os.Getenv("CONFIG_MAP_NAME")
|
||||
if configMapRawName != "" {
|
||||
err := config.ConfigMapName.Decode(configMapRawName)
|
||||
|
||||
operatorNamespace := spec.GetOperatorNamespace()
|
||||
config.Namespace = operatorNamespace
|
||||
|
||||
namespacedConfigMapName := operatorNamespace + "/" + configMapRawName
|
||||
|
||||
log.Printf("Looking for the operator configmap at the same namespace the operator resides. Fully qualified configmap name: %v", namespacedConfigMapName)
|
||||
|
||||
err := config.ConfigMapName.Decode(namespacedConfigMapName)
|
||||
if err != nil {
|
||||
log.Fatalf("incorrect config map name")
|
||||
log.Fatalf("incorrect config map name: %v", namespacedConfigMapName)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
|
|
|||
|
|
@ -105,8 +105,8 @@ func (c *Controller) initOperatorConfig() {
|
|||
}
|
||||
|
||||
if configMapData["watched_namespace"] == "" {
|
||||
c.logger.Infoln("No namespace to watch specified. Fall back to watching the 'default' namespace.")
|
||||
configMapData["watched_namespace"] = v1.NamespaceDefault
|
||||
c.logger.Infof("No namespace to watch specified. By convention, the operator falls back to watching the namespace it is deployed to: '%v' \n", c.config.Namespace)
|
||||
configMapData["watched_namespace"] = c.config.Namespace
|
||||
}
|
||||
|
||||
if c.config.NoDatabaseAccess {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package spec
|
|||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -26,6 +28,8 @@ const (
|
|||
EventUpdate EventType = "UPDATE"
|
||||
EventDelete EventType = "DELETE"
|
||||
EventSync EventType = "SYNC"
|
||||
|
||||
fileWithNamespace = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
|
||||
)
|
||||
|
||||
// ClusterEvent carries the payload of the Cluster TPR events.
|
||||
|
|
@ -161,20 +165,38 @@ func (n NamespacedName) MarshalJSON() ([]byte, error) {
|
|||
|
||||
// Decode converts a (possibly unqualified) string into the namespaced name object.
|
||||
func (n *NamespacedName) Decode(value string) error {
|
||||
return n.DecodeWorker(value, GetOperatorNamespace())
|
||||
}
|
||||
|
||||
// DecodeWorker separates the decode logic to (unit) test
|
||||
// from obtaining the operator namespace that depends on k8s mounting files at runtime
|
||||
func (n *NamespacedName) DecodeWorker(value, operatorNamespace string) error {
|
||||
name := types.NewNamespacedNameFromString(value)
|
||||
|
||||
if strings.Trim(value, string(types.Separator)) != "" && name == (types.NamespacedName{}) {
|
||||
name.Name = value
|
||||
name.Namespace = v1.NamespaceDefault
|
||||
name.Namespace = operatorNamespace
|
||||
} else if name.Namespace == "" {
|
||||
name.Namespace = v1.NamespaceDefault
|
||||
name.Namespace = operatorNamespace
|
||||
}
|
||||
|
||||
if name.Name == "" {
|
||||
return fmt.Errorf("incorrect namespaced name")
|
||||
return fmt.Errorf("incorrect namespaced name: %v", value)
|
||||
}
|
||||
|
||||
*n = NamespacedName(name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOperatorNamespace assumes serviceaccount secret is mounted by kubernetes
|
||||
// Placing this func here instead of pgk/util avoids circular import
|
||||
func GetOperatorNamespace() string {
|
||||
|
||||
operatorNamespaceBytes, err := ioutil.ReadFile(fileWithNamespace)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to detect operator namespace from within its pod due to: %v", err)
|
||||
}
|
||||
|
||||
return string(operatorNamespaceBytes)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,22 +5,27 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
mockOperatorNamespace = "acid"
|
||||
)
|
||||
|
||||
var nnTests = []struct {
|
||||
s string
|
||||
expected NamespacedName
|
||||
expectedMarshal []byte
|
||||
}{
|
||||
{`acid/cluster`, NamespacedName{Namespace: "acid", Name: "cluster"}, []byte(`"acid/cluster"`)},
|
||||
{`/name`, NamespacedName{Namespace: "default", Name: "name"}, []byte(`"default/name"`)},
|
||||
{`test`, NamespacedName{Namespace: "default", Name: "test"}, []byte(`"default/test"`)},
|
||||
{`acid/cluster`, NamespacedName{Namespace: mockOperatorNamespace, Name: "cluster"}, []byte(`"acid/cluster"`)},
|
||||
{`/name`, NamespacedName{Namespace: mockOperatorNamespace, Name: "name"}, []byte(`"acid/name"`)},
|
||||
{`test`, NamespacedName{Namespace: mockOperatorNamespace, Name: "test"}, []byte(`"acid/test"`)},
|
||||
}
|
||||
|
||||
var nnErr = []string{"test/", "/", "", "//"}
|
||||
|
||||
func TestNamespacedNameDecode(t *testing.T) {
|
||||
|
||||
for _, tt := range nnTests {
|
||||
var actual NamespacedName
|
||||
err := actual.Decode(tt.s)
|
||||
err := actual.DecodeWorker(tt.s, mockOperatorNamespace)
|
||||
if err != nil {
|
||||
t.Errorf("decode error: %v", err)
|
||||
}
|
||||
|
|
@ -28,6 +33,7 @@ func TestNamespacedNameDecode(t *testing.T) {
|
|||
t.Errorf("expected: %v, got %#v", tt.expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNamespacedNameMarshal(t *testing.T) {
|
||||
|
|
@ -47,7 +53,7 @@ func TestNamespacedNameMarshal(t *testing.T) {
|
|||
func TestNamespacedNameError(t *testing.T) {
|
||||
for _, tt := range nnErr {
|
||||
var actual NamespacedName
|
||||
err := actual.Decode(tt)
|
||||
err := actual.DecodeWorker(tt, mockOperatorNamespace)
|
||||
if err == nil {
|
||||
t.Errorf("error expected for %q, got: %#v", tt, actual)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ type KubernetesClient struct {
|
|||
v1core.PersistentVolumeClaimsGetter
|
||||
v1core.ConfigMapsGetter
|
||||
v1core.NodesGetter
|
||||
v1core.ServiceAccountsGetter
|
||||
v1beta1.StatefulSetsGetter
|
||||
policyv1beta1.PodDisruptionBudgetsGetter
|
||||
apiextbeta1.CustomResourceDefinitionsGetter
|
||||
|
|
@ -72,6 +73,7 @@ func NewFromConfig(cfg *rest.Config) (KubernetesClient, error) {
|
|||
kubeClient.ServicesGetter = client.CoreV1()
|
||||
kubeClient.EndpointsGetter = client.CoreV1()
|
||||
kubeClient.SecretsGetter = client.CoreV1()
|
||||
kubeClient.ServiceAccountsGetter = client.CoreV1()
|
||||
kubeClient.ConfigMapsGetter = client.CoreV1()
|
||||
kubeClient.PersistentVolumeClaimsGetter = client.CoreV1()
|
||||
kubeClient.PersistentVolumesGetter = client.CoreV1()
|
||||
|
|
|
|||
Loading…
Reference in New Issue