Merge branch 'master' into update-ebs-tags-inherit-labels

This commit is contained in:
Felix Kunde
2026-07-21 08:55:56 +02:00
committed by GitHub
23 changed files with 159 additions and 15 deletions
@@ -252,6 +252,7 @@ type AWSGCPConfiguration struct {
WALAZStorageAccount string `json:"wal_az_storage_account,omitempty"`
LogS3Bucket string `json:"log_s3_bucket,omitempty"`
KubeIAMRole string `json:"kube_iam_role,omitempty"`
IRSARoleARN string `json:"irsa_role_arn,omitempty"`
AdditionalSecretMount string `json:"additional_secret_mount,omitempty"`
AdditionalSecretMountPath string `json:"additional_secret_mount_path,omitempty"`
EnableEBSGp3Migration bool `json:"enable_ebs_gp3_migration,omitempty"`
@@ -77,6 +77,8 @@ spec:
type: integer
gcp_credentials:
type: string
irsa_role_arn:
type: string
kube_iam_role:
type: string
log_s3_bucket:
+6
View File
@@ -1140,6 +1140,12 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
c.logger.Infof("Storage resize is disabled (storage_resize_mode is off). Skipping volume size sync.")
}
// Pod service account (IRSA annotation sync)
if err := c.syncPodServiceAccount(); err != nil {
c.logger.Errorf("could not sync pod service account: %v", err)
updateFailed = true
}
// Statefulset
func() {
if err := c.syncStatefulSet(); err != nil {
+6
View File
@@ -2278,6 +2278,12 @@ func (c *Cluster) generatePrimaryPodDisruptionBudget() *policyv1.PodDisruptionBu
labels[c.OpConfig.PodRoleLabel] = string(Master)
}
// When master selector is disabled and synchronous_mode_strict is on, require
// master + synchronous_node_count (default 1) healthy pods for write quorum.
if pdbMasterLabelSelector != nil && !*pdbMasterLabelSelector && minAvailable.IntVal > 0 && c.Spec.SynchronousModeStrict {
minAvailable = intstr.FromInt32(int32(c.Spec.SynchronousNodeCount + 1))
}
return &policyv1.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: c.PrimaryPodDisruptionBudgetName(),
+2 -2
View File
@@ -2691,13 +2691,13 @@ func TestGeneratePodDisruptionBudget(t *testing.T) {
k8sutil.KubernetesClient{},
acidv1.Postgresql{
ObjectMeta: metav1.ObjectMeta{Name: "myapp-database", Namespace: "myapp"},
Spec: acidv1.PostgresSpec{TeamID: "myapp", NumberOfInstances: 3}},
Spec: acidv1.PostgresSpec{TeamID: "myapp", NumberOfInstances: 3, Patroni: acidv1.Patroni{SynchronousModeStrict: true, SynchronousNodeCount: 1}}},
logger,
eventRecorder),
check: []func(cluster *Cluster, podDisruptionBudget *policyv1.PodDisruptionBudget) error{
testPodDisruptionBudgetOwnerReference,
hasName("postgres-myapp-database-pdb"),
hasMinAvailable(1),
hasMinAvailable(2),
testLabelsAndSelectors(true),
},
},
+68
View File
@@ -107,6 +107,10 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
}
}
if err = c.syncPodServiceAccount(); err != nil {
c.logger.Errorf("could not sync pod service account: %v", err)
}
if err = c.syncStatefulSet(); err != nil {
if !k8sutil.ResourceAlreadyExists(err) {
err = fmt.Errorf("could not sync statefulsets: %v", err)
@@ -630,6 +634,10 @@ func (c *Cluster) syncStatefulSet() error {
if !cmp.rollingUpdate {
updatedPodAnnotations := map[string]*string{}
for _, anno := range cmp.deletedPodAnnotations {
// during IRSA migration let kube2iam annotation drain naturally via pod rotation
if c.OpConfig.IRSARoleARN != "" && anno == constants.KubeIAmAnnotation {
continue
}
updatedPodAnnotations[anno] = nil
}
for anno, val := range desiredSts.Spec.Template.Annotations {
@@ -1803,6 +1811,7 @@ func (c *Cluster) syncLogicalBackupJob() error {
// no existing logical backup job, create new one
c.logger.Info("could not find the cluster's logical backup job")
if err = c.createLogicalBackupJob(); err == nil {
c.logger.Infof("created missing logical backup job %s", jobName)
} else {
@@ -1817,3 +1826,62 @@ func (c *Cluster) syncLogicalBackupJob() error {
return nil
}
func (c *Cluster) syncPodServiceAccount() error {
sa, err := c.KubeClient.ServiceAccounts(c.Namespace).Get(context.TODO(), c.OpConfig.PodServiceAccountName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("could not get pod service account %q: %v", c.OpConfig.PodServiceAccountName, err)
}
changed := false
if c.OpConfig.IRSARoleARN != "" {
if val, ok := sa.Annotations[constants.IRSAAnnotation]; !ok || val != c.OpConfig.IRSARoleARN {
if sa.Annotations == nil {
sa.Annotations = make(map[string]string)
}
sa.Annotations[constants.IRSAAnnotation] = c.OpConfig.IRSARoleARN
changed = true
}
} else {
if _, ok := sa.Annotations[constants.IRSAAnnotation]; ok {
delete(sa.Annotations, constants.IRSAAnnotation)
changed = true
}
}
if changed {
if _, err = c.KubeClient.ServiceAccounts(c.Namespace).Update(context.TODO(), sa, metav1.UpdateOptions{}); err != nil {
return fmt.Errorf("could not update pod service account %q: %v", sa.Name, err)
}
c.logger.Infof("synced annotations on pod service account %q", sa.Name)
}
if c.OpConfig.IRSARoleARN != "" {
c.logIRSAMigrationProgress()
}
return nil
}
func (c *Cluster) logIRSAMigrationProgress() {
pods, err := c.listPods()
if err != nil {
c.logger.Warnf("IRSA migration: could not list pods: %v", err)
return
}
total := len(pods)
remaining := 0
for _, pod := range pods {
if _, ok := pod.Annotations[constants.KubeIAmAnnotation]; ok {
remaining++
}
}
if remaining > 0 {
c.logger.Infof("IRSA migration in progress: %d/%d pods still carry kube2iam annotation, will be removed on next rotation", remaining, total)
} else {
c.logger.Infof("IRSA migration complete: all %d pods have rotated, kube2iam annotation fully drained", total)
}
}
+1
View File
@@ -53,6 +53,7 @@ func newFakeK8sAnnotationsClient() (k8sutil.KubernetesClient, *k8sFake.Clientset
EndpointsGetter: clientSet.CoreV1(),
ConfigMapsGetter: clientSet.CoreV1(),
PodsGetter: clientSet.CoreV1(),
ServiceAccountsGetter: clientSet.CoreV1(),
DeploymentsGetter: clientSet.AppsV1(),
CronJobsGetter: clientSet.BatchV1(),
}, clientSet
+6
View File
@@ -247,6 +247,12 @@ func (c *Controller) initPodServiceAccount() {
c.PodServiceAccount.Name = c.opConfig.PodServiceAccountName
}
c.PodServiceAccount.Namespace = ""
if c.opConfig.IRSARoleARN != "" {
if c.PodServiceAccount.Annotations == nil {
c.PodServiceAccount.Annotations = make(map[string]string)
}
c.PodServiceAccount.Annotations[constants.IRSAAnnotation] = c.opConfig.IRSARoleARN
}
}
// actual service accounts are deployed at the time of Postgres/Spilo cluster creation
+1
View File
@@ -175,6 +175,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
result.AWSRegion = fromCRD.AWSGCP.AWSRegion
result.LogS3Bucket = fromCRD.AWSGCP.LogS3Bucket
result.KubeIAMRole = fromCRD.AWSGCP.KubeIAMRole
result.IRSARoleARN = fromCRD.AWSGCP.IRSARoleARN
result.WALGSBucket = fromCRD.AWSGCP.WALGSBucket
result.GCPCredentials = fromCRD.AWSGCP.GCPCredentials
result.WALAZStorageAccount = fromCRD.AWSGCP.WALAZStorageAccount
+1
View File
@@ -194,6 +194,7 @@ type Config struct {
WALES3Bucket string `name:"wal_s3_bucket"`
LogS3Bucket string `name:"log_s3_bucket"`
KubeIAMRole string `name:"kube_iam_role"`
IRSARoleARN string `name:"irsa_role_arn"`
WALGSBucket string `name:"wal_gs_bucket"`
GCPCredentials string `name:"gcp_credentials"`
WALAZStorageAccount string `name:"wal_az_storage_account"`
+1
View File
@@ -4,6 +4,7 @@ package constants
const (
ZalandoDNSNameAnnotation = "external-dns.alpha.kubernetes.io/hostname"
KubeIAmAnnotation = "iam.amazonaws.com/role"
IRSAAnnotation = "eks.amazonaws.com/role-arn"
VolumeStorateProvisionerAnnotation = "pv.kubernetes.io/provisioned-by"
PostgresqlControllerAnnotationKey = "acid.zalan.do/controller"
)