diff --git a/docs/user.md b/docs/user.md index d0ebcc1b0..53a260220 100644 --- a/docs/user.md +++ b/docs/user.md @@ -242,3 +242,35 @@ metadata: Note that timezone required for `timestamp` (offset relative to UTC, see RFC 3339 section 5.6) + + +## Sidecar Support + +Each cluster can specify arbitrary sidecars to run. These containers could be used for +log aggregation, monitoring, backups or other tasks. A sidecar can be specified like this: + +```yaml +apiVersion: "acid.zalan.do/v1" +kind: postgresql + +metadata: + name: acid-minimal-cluster +spec: + ... + sidecars: + - name: "container-name" + image: "company/image:tag" + env: + - name: "ENV_VAR_NAME" + value: "any-k8s-env-things" +``` + +In addition to any environment variables you specify, the following environment variables +are always passed to sidecars: + + - `POD_NAME` - field reference to `metadata.name` + - `POD_NAMESPACE` - field reference to `metadata.namespace` + - `POSTGRES_USER` - the superuser that can be used to connect to the database + - `POSTGRES_PASSWORD` - the password for the superuser + +The PostgreSQL volume is shared with sidecars and is mounted at `/home/postgres/pgdata`. diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index f216797c0..d6e249708 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -318,8 +318,9 @@ func (c *Cluster) generatePodTemplate( patroniParameters *spec.Patroni, cloneDescription *spec.CloneDescription, dockerImage *string, + sidecars *[]spec.Sidecar, customPodEnvVars map[string]string, -) *v1.PodTemplateSpec { +) (*v1.PodTemplateSpec, error) { spiloConfiguration := c.generateSpiloJSONConfiguration(pgParameters, patroniParameters) envVars := []v1.EnvVar{ @@ -525,6 +526,16 @@ func (c *Cluster) generatePodTemplate( ) } + if sidecars != nil && len(*sidecars) > 0 { + for index, sidecar := range *sidecars { + sc, err := c.getSidecarContainer(sidecar, index, volumeMounts) + if err != nil { + return nil, err + } + podSpec.Containers = append(podSpec.Containers, *sc) + } + } + template := v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: c.labelsSet(true), @@ -536,7 +547,72 @@ func (c *Cluster) generatePodTemplate( template.Annotations = map[string]string{constants.KubeIAmAnnotation: c.OpConfig.KubeIAMRole} } - return &template + return &template, nil +} + +func (c *Cluster) getSidecarContainer(sidecar spec.Sidecar, index int, volumeMounts []v1.VolumeMount) (*v1.Container, error) { + name := sidecar.Name + if name == "" { + name = fmt.Sprintf("sidecar-%d", index) + } + resources, err := c.resourceRequirements( + makeResources( + sidecar.Resources.ResourceRequest.CPU, + sidecar.Resources.ResourceRequest.Memory, + sidecar.Resources.ResourceLimits.CPU, + sidecar.Resources.ResourceLimits.Memory, + ), + ) + if err != nil { + return nil, err + } + env := []v1.EnvVar{ + { + Name: "POD_NAME", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "POSTGRES_USER", + Value: c.OpConfig.SuperUsername, + }, + { + Name: "POSTGRES_PASSWORD", + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: c.credentialSecretName(c.OpConfig.SuperUsername), + }, + Key: "password", + }, + }, + }, + } + if len(sidecar.Env) > 0 { + env = append(env, sidecar.Env...) + } + return &v1.Container{ + Name: name, + Image: sidecar.DockerImage, + ImagePullPolicy: v1.PullIfNotPresent, + Resources: *resources, + VolumeMounts: volumeMounts, + Env: env, + Ports: sidecar.Ports, + }, nil } func getBucketScopeSuffix(uid string) string { @@ -583,7 +659,10 @@ func (c *Cluster) generateStatefulSet(spec *spec.PostgresSpec) (*v1beta1.Statefu customPodEnvVars = cm.Data } } - podTemplate := c.generatePodTemplate(c.Postgresql.GetUID(), resourceRequirements, resourceRequirementsScalyrSidecar, &spec.Tolerations, &spec.PostgresqlParam, &spec.Patroni, &spec.Clone, &spec.DockerImage, customPodEnvVars) + podTemplate, err := c.generatePodTemplate(c.Postgresql.GetUID(), resourceRequirements, resourceRequirementsScalyrSidecar, &spec.Tolerations, &spec.PostgresqlParam, &spec.Patroni, &spec.Clone, &spec.DockerImage, &spec.Sidecars, customPodEnvVars) + if err != nil { + return nil, fmt.Errorf("could not generate pod template: %v", err) + } volumeClaimTemplate, err := generatePersistentVolumeClaimTemplate(spec.Volume.Size, spec.Volume.StorageClass) if err != nil { return nil, fmt.Errorf("could not generate volume claim template: %v", err) diff --git a/pkg/spec/postgresql.go b/pkg/spec/postgresql.go index bd5d06127..d59ccd22c 100644 --- a/pkg/spec/postgresql.go +++ b/pkg/spec/postgresql.go @@ -61,6 +61,15 @@ type CloneDescription struct { EndTimestamp string `json:"timestamp,omitempty"` } +// Sidecar defines a container to be run in the same pod as the Postgres container. +type Sidecar struct { + Resources `json:"resources,omitempty"` + Name string `json:"name,omitempty"` + DockerImage string `json:"image,omitempty"` + Ports []v1.ContainerPort `json:"ports,omitempty"` + Env []v1.EnvVar `json:"env,omitempty"` +} + type UserFlags []string // PostgresStatus contains status of the PostgreSQL cluster (running, creation failed etc.) @@ -124,6 +133,7 @@ type PostgresSpec struct { ClusterName string `json:"-"` Databases map[string]string `json:"databases,omitempty"` Tolerations []v1.Toleration `json:"tolerations,omitempty"` + Sidecars []Sidecar `json:"sidecars,omitempty"` } // PostgresqlList defines a list of PostgreSQL clusters.