Custom annotations 329 (#657)
* Add ability for custom annotations to database pods
This commit is contained in:
parent
33e1d60703
commit
535517cd1b
|
|
@ -53,9 +53,11 @@ configKubernetes:
|
|||
cluster_domain: cluster.local
|
||||
# additional labels assigned to the cluster objects
|
||||
cluster_labels:
|
||||
application: spilo
|
||||
application: spilo
|
||||
# label assigned to Kubernetes objects created by the operator
|
||||
cluster_name_label: cluster-name
|
||||
# additional annotations to add to every database pod
|
||||
custom_pod_annotations:
|
||||
# toggles pod anti affinity on the Postgres pods
|
||||
enable_pod_antiaffinity: false
|
||||
# toggles PDB to set to MinAvailabe 0 or 1
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ configKubernetes:
|
|||
cluster_labels: application:spilo
|
||||
# label assigned to Kubernetes objects created by the operator
|
||||
cluster_name_label: version
|
||||
# annotations attached to each database pod
|
||||
# custom_pod_annotations: keya:valuea
|
||||
# toggles pod anti affinity on the Postgres pods
|
||||
enable_pod_antiaffinity: "false"
|
||||
# toggles PDB to set to MinAvailabe 0 or 1
|
||||
|
|
@ -127,8 +129,7 @@ configLoadBalancer:
|
|||
# DNS zone for cluster DNS name when load balancer is configured for cluster
|
||||
db_hosted_zone: db.example.com
|
||||
# annotations to apply to service when load balancing is enabled
|
||||
# custom_service_annotations:
|
||||
# "keyx:valuez,keya:valuea"
|
||||
# custom_service_annotations: "keyx:valuez,keya:valuea"
|
||||
|
||||
# toggles service type load balancer pointing to the master pod of the cluster
|
||||
enable_master_load_balancer: "true"
|
||||
|
|
|
|||
|
|
@ -118,6 +118,11 @@ These parameters are grouped directly under the `spec` key in the manifest.
|
|||
then the default priority class is taken. The priority class itself must be
|
||||
defined in advance. Optional.
|
||||
|
||||
* **podAnnotations**
|
||||
A map of key value pairs that gets attached as [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)
|
||||
to each pod created for the database.
|
||||
|
||||
|
||||
* **enableShmVolume**
|
||||
Start a database pod without limitations on shm memory. By default docker
|
||||
limit `/dev/shm` to `64M` (see e.g. the [docker
|
||||
|
|
|
|||
|
|
@ -168,6 +168,11 @@ configuration they are grouped under the `kubernetes` key.
|
|||
Postgres pods are [terminated forcefully](https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods)
|
||||
after this timeout. The default is `5m`.
|
||||
|
||||
* **custom_pod_annotations**
|
||||
This key/value map provides a list of annotations that get attached to each pod
|
||||
of a database created by the operator. If the annotation key is also provided
|
||||
by the database definition, the database definition value is used.
|
||||
|
||||
* **watched_namespace**
|
||||
The operator watches for Postgres objects in the given namespace. If not
|
||||
specified, the value is taken from the operator namespace. A special `*`
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ spec:
|
|||
- 127.0.0.1/32
|
||||
databases:
|
||||
foo: zalando
|
||||
|
||||
# podAnnotations:
|
||||
# annotation.key: value
|
||||
# Expert section
|
||||
|
||||
enableShmVolume: true
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ data:
|
|||
cluster_history_entries: "1000"
|
||||
cluster_labels: application:spilo
|
||||
cluster_name_label: version
|
||||
# custom_service_annotations:
|
||||
# "keyx:valuez,keya:valuea"
|
||||
# custom_service_annotations: "keyx:valuez,keya:valuea"
|
||||
# custom_pod_annotations: "keya:valuea"
|
||||
db_hosted_zone: db.example.com
|
||||
debug_logging: "true"
|
||||
# default_cpu_limit: "3"
|
||||
|
|
@ -37,7 +37,7 @@ data:
|
|||
# logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup"
|
||||
# logical_backup_s3_bucket: "my-bucket-url"
|
||||
# logical_backup_schedule: "30 00 * * *"
|
||||
master_dns_name_format: '{cluster}.{team}.staging.{hostedzone}'
|
||||
master_dns_name_format: "{cluster}.{team}.staging.{hostedzone}"
|
||||
# master_pod_move_timeout: 10m
|
||||
# max_instances: "-1"
|
||||
# min_instances: "-1"
|
||||
|
|
@ -60,13 +60,13 @@ data:
|
|||
ready_wait_interval: 3s
|
||||
ready_wait_timeout: 30s
|
||||
repair_period: 5m
|
||||
replica_dns_name_format: '{cluster}-repl.{team}.staging.{hostedzone}'
|
||||
replica_dns_name_format: "{cluster}-repl.{team}.staging.{hostedzone}"
|
||||
replication_username: standby
|
||||
resource_check_interval: 3s
|
||||
resource_check_timeout: 10m
|
||||
resync_period: 5m
|
||||
ring_log_lines: "100"
|
||||
secret_name_template: '{username}.{cluster}.credentials'
|
||||
secret_name_template: "{username}.{cluster}.credentials"
|
||||
# sidecar_docker_images: ""
|
||||
# set_memory_request_to_limit: "false"
|
||||
spilo_privileged: "false"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ configuration:
|
|||
cluster_labels:
|
||||
application: spilo
|
||||
cluster_name_label: cluster-name
|
||||
# custom_pod_annotations:
|
||||
# keya: valuea
|
||||
# keyb: valueb
|
||||
enable_pod_antiaffinity: false
|
||||
enable_pod_disruption_budget: true
|
||||
# infrastructure_roles_secret_name: ""
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ type KubernetesMetaConfiguration struct {
|
|||
InheritedLabels []string `json:"inherited_labels,omitempty"`
|
||||
ClusterNameLabel string `json:"cluster_name_label,omitempty"`
|
||||
NodeReadinessLabel map[string]string `json:"node_readiness_label,omitempty"`
|
||||
CustomPodAnnotations map[string]string `json:"custom_pod_annotations,omitempty"`
|
||||
// TODO: use a proper toleration structure?
|
||||
PodToleration map[string]string `json:"toleration,omitempty"`
|
||||
// TODO: use namespacedname
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ type PostgresSpec struct {
|
|||
EnableLogicalBackup bool `json:"enableLogicalBackup,omitempty"`
|
||||
LogicalBackupSchedule string `json:"logicalBackupSchedule,omitempty"`
|
||||
StandbyCluster *StandbyDescription `json:"standby"`
|
||||
PodAnnotations map[string]string `json:"podAnnotations"`
|
||||
|
||||
// deprecated json tags
|
||||
InitContainersOld []v1.Container `json:"init_containers,omitempty"`
|
||||
|
|
|
|||
|
|
@ -437,6 +437,16 @@ var postgresqlList = []struct {
|
|||
PostgresqlList{},
|
||||
errors.New("unexpected end of JSON input")}}
|
||||
|
||||
var annotations = []struct {
|
||||
in []byte
|
||||
annotations map[string]string
|
||||
err error
|
||||
}{{
|
||||
in: []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "acid-testcluster1"}, "spec": {"podAnnotations": {"foo": "bar"},"teamId": "acid", "clone": {"cluster": "team-batman"}}}`),
|
||||
annotations: map[string]string{"foo": "bar"},
|
||||
err: nil},
|
||||
}
|
||||
|
||||
func mustParseTime(s string) metav1.Time {
|
||||
v, err := time.Parse("15:04", s)
|
||||
if err != nil {
|
||||
|
|
@ -482,6 +492,25 @@ func TestWeekdayTime(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClusterAnnotations(t *testing.T) {
|
||||
for _, tt := range annotations {
|
||||
var cluster Postgresql
|
||||
err := cluster.UnmarshalJSON(tt.in)
|
||||
if err != nil {
|
||||
if tt.err == nil || err.Error() != tt.err.Error() {
|
||||
t.Errorf("Unable to marshal cluster with annotations: expected %v got %v", tt.err, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
for k, v := range cluster.Spec.PodAnnotations {
|
||||
found, expected := v, tt.annotations[k]
|
||||
if found != expected {
|
||||
t.Errorf("Didn't find correct value for key %v in for podAnnotations: Expected %v found %v", k, expected, found)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClusterName(t *testing.T) {
|
||||
for _, tt := range clusterNames {
|
||||
name, err := extractClusterName(tt.in, tt.inTeam)
|
||||
|
|
|
|||
|
|
@ -102,6 +102,13 @@ func (in *KubernetesMetaConfiguration) DeepCopyInto(out *KubernetesMetaConfigura
|
|||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.CustomPodAnnotations != nil {
|
||||
in, out := &in.CustomPodAnnotations, &out.CustomPodAnnotations
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.PodToleration != nil {
|
||||
in, out := &in.PodToleration, &out.PodToleration
|
||||
*out = make(map[string]string, len(*in))
|
||||
|
|
@ -513,6 +520,13 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) {
|
|||
*out = new(StandbyDescription)
|
||||
**out = **in
|
||||
}
|
||||
if in.PodAnnotations != nil {
|
||||
in, out := &in.PodAnnotations, &out.PodAnnotations
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.InitContainersOld != nil {
|
||||
in, out := &in.InitContainersOld, &out.InitContainersOld
|
||||
*out = make([]corev1.Container, len(*in))
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/zalando/postgres-operator/pkg/util/config"
|
||||
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
|
||||
"github.com/zalando/postgres-operator/pkg/util/teams"
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -328,3 +328,57 @@ func TestShouldDeleteSecret(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodAnnotations(t *testing.T) {
|
||||
testName := "TestPodAnnotations"
|
||||
tests := []struct {
|
||||
subTest string
|
||||
operator map[string]string
|
||||
database map[string]string
|
||||
merged map[string]string
|
||||
}{
|
||||
{
|
||||
subTest: "No Annotations",
|
||||
operator: make(map[string]string),
|
||||
database: make(map[string]string),
|
||||
merged: make(map[string]string),
|
||||
},
|
||||
{
|
||||
subTest: "Operator Config Annotations",
|
||||
operator: map[string]string{"foo": "bar"},
|
||||
database: make(map[string]string),
|
||||
merged: map[string]string{"foo": "bar"},
|
||||
},
|
||||
{
|
||||
subTest: "Database Config Annotations",
|
||||
operator: make(map[string]string),
|
||||
database: map[string]string{"foo": "bar"},
|
||||
merged: map[string]string{"foo": "bar"},
|
||||
},
|
||||
{
|
||||
subTest: "Database Config overrides Operator Config Annotations",
|
||||
operator: map[string]string{"foo": "bar", "global": "foo"},
|
||||
database: map[string]string{"foo": "baz", "local": "foo"},
|
||||
merged: map[string]string{"foo": "baz", "global": "foo", "local": "foo"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
cl.OpConfig.CustomPodAnnotations = tt.operator
|
||||
cl.Postgresql.Spec.PodAnnotations = tt.database
|
||||
|
||||
annotations := cl.generatePodAnnotations(&cl.Postgresql.Spec)
|
||||
for k, v := range annotations {
|
||||
if observed, expected := v, tt.merged[k]; observed != expected {
|
||||
t.Errorf("%v expects annotation value %v for key %v, but found %v",
|
||||
testName+"/"+tt.subTest, expected, observed, k)
|
||||
}
|
||||
}
|
||||
for k, v := range tt.merged {
|
||||
if observed, expected := annotations[k], v; observed != expected {
|
||||
t.Errorf("%v expects annotation value %v for key %v, but found %v",
|
||||
testName+"/"+tt.subTest, expected, observed, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -430,6 +430,7 @@ func mountShmVolumeNeeded(opConfig config.Config, pgSpec *acidv1.PostgresSpec) *
|
|||
func generatePodTemplate(
|
||||
namespace string,
|
||||
labels labels.Set,
|
||||
annotations map[string]string,
|
||||
spiloContainer *v1.Container,
|
||||
initContainers []v1.Container,
|
||||
sidecarContainers []v1.Container,
|
||||
|
|
@ -485,13 +486,17 @@ func generatePodTemplate(
|
|||
|
||||
template := v1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: labels,
|
||||
Namespace: namespace,
|
||||
Labels: labels,
|
||||
Namespace: namespace,
|
||||
Annotations: annotations,
|
||||
},
|
||||
Spec: podSpec,
|
||||
}
|
||||
if kubeIAMRole != "" {
|
||||
template.Annotations = map[string]string{constants.KubeIAmAnnotation: kubeIAMRole}
|
||||
if template.Annotations == nil{
|
||||
template.Annotations = make(map[string]string)
|
||||
}
|
||||
template.Annotations[constants.KubeIAmAnnotation] = kubeIAMRole
|
||||
}
|
||||
|
||||
return &template, nil
|
||||
|
|
@ -881,10 +886,13 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
|
|||
effectiveFSGroup = spec.SpiloFSGroup
|
||||
}
|
||||
|
||||
annotations := c.generatePodAnnotations(spec)
|
||||
|
||||
// generate pod template for the statefulset, based on the spilo container and sidecars
|
||||
if podTemplate, err = generatePodTemplate(
|
||||
c.Namespace,
|
||||
c.labelsSet(true),
|
||||
annotations,
|
||||
spiloContainer,
|
||||
spec.InitContainers,
|
||||
sidecarContainers,
|
||||
|
|
@ -949,6 +957,24 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
|
|||
return statefulSet, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) generatePodAnnotations(spec *acidv1.PostgresSpec) map[string]string {
|
||||
annotations := make(map[string]string)
|
||||
for k, v := range c.OpConfig.CustomPodAnnotations {
|
||||
annotations[k] = v
|
||||
}
|
||||
if spec != nil || spec.PodAnnotations != nil {
|
||||
for k, v := range spec.PodAnnotations {
|
||||
annotations[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(annotations) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return annotations
|
||||
}
|
||||
|
||||
func generateScalyrSidecarSpec(clusterName, APIKey, serverURL, dockerImage string,
|
||||
containerResources *acidv1.Resources, logger *logrus.Entry) *acidv1.Sidecar {
|
||||
if APIKey == "" || dockerImage == "" {
|
||||
|
|
@ -1462,10 +1488,13 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1beta1.CronJob, error) {
|
|||
},
|
||||
}}
|
||||
|
||||
annotations := c.generatePodAnnotations(&c.Spec)
|
||||
|
||||
// re-use the method that generates DB pod templates
|
||||
if podTemplate, err = generatePodTemplate(
|
||||
c.Namespace,
|
||||
labels,
|
||||
annotations,
|
||||
logicalBackupContainer,
|
||||
[]v1.Container{},
|
||||
[]v1.Container{},
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
|||
result.ReplicationUsername = fromCRD.PostgresUsersConfiguration.ReplicationUsername
|
||||
|
||||
// kubernetes config
|
||||
result.CustomPodAnnotations = fromCRD.Kubernetes.CustomPodAnnotations
|
||||
result.PodServiceAccountName = fromCRD.Kubernetes.PodServiceAccountName
|
||||
result.PodServiceAccountDefinition = fromCRD.Kubernetes.PodServiceAccountDefinition
|
||||
result.PodServiceAccountRoleBindingDefinition = fromCRD.Kubernetes.PodServiceAccountRoleBindingDefinition
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ type Config struct {
|
|||
EnableMasterLoadBalancer bool `name:"enable_master_load_balancer" default:"true"`
|
||||
EnableReplicaLoadBalancer bool `name:"enable_replica_load_balancer" default:"false"`
|
||||
CustomServiceAnnotations map[string]string `name:"custom_service_annotations"`
|
||||
CustomPodAnnotations map[string]string `name:"custom_pod_annotations"`
|
||||
EnablePodAntiAffinity bool `name:"enable_pod_antiaffinity" default:"false"`
|
||||
PodAntiAffinityTopologyKey string `name:"pod_antiaffinity_topology_key" default:"kubernetes.io/hostname"`
|
||||
// deprecated and kept for backward compatibility
|
||||
|
|
|
|||
Loading…
Reference in New Issue