kubernetes/kops · error

error creating ServiceAccount %q: %w

Error message

error creating ServiceAccount %q: %w

What it means

RenderGCE wraps a failed google.golang.org/api IAM ServiceAccounts.Create call. When the task is not shared and no existing account was found, kOps creates a new service account via the GCP IAM API; any API error (permission denied, quota, invalid account ID, conflict, transient) is wrapped verbatim here.

Source

Thrown at upup/pkg/fi/cloudup/gcetasks/serviceaccount.go:136

		return err
	}

	fqn := "projects/" + projectID + "/serviceAccounts/" + email

	if a == nil {
		klog.V(2).Infof("Creating ServiceAccount %q", fqn)

		sa := &iam.CreateServiceAccountRequest{
			AccountId: accountID,
			ServiceAccount: &iam.ServiceAccount{
				Description: fi.ValueOf(e.Description),
				DisplayName: fi.ValueOf(e.DisplayName),
			},
		}

		created, err := cloud.IAM().ServiceAccounts().Create(ctx, "projects/"+projectID, sa)
		if err != nil {
			return fmt.Errorf("error creating ServiceAccount %q: %w", fqn, err)
		}
		if created.Email != email {
			return fmt.Errorf("created ServiceAccount did not have expected email; got %q; want %q", created.Email, email)
		}
	} else {
		if changes.Description != nil || changes.DisplayName != nil {
			sa := &iam.ServiceAccount{
				Email:       email,
				Description: fi.ValueOf(e.Description),
				DisplayName: fi.ValueOf(e.DisplayName),
			}

			_, err := cloud.IAM().ServiceAccounts().Update(ctx, fqn, sa)
			if err != nil {
				return fmt.Errorf("error creating ServiceAccount %q: %w", fqn, err)
			}

			changes.Description = nil

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Read the wrapped %w error to get the GCP status code, then act on it (403 => grant the role, 409 => account already exists, 400 => fix the account ID).
  2. Grant the acting identity `roles/iam.serviceAccountAdmin` (or Owner/Editor) on the project.
  3. Check the account ID derived from the email: max 30 chars, letters/digits/hyphens, must start with a letter (gce.SplitServiceAccountEmail input).
  4. For 409, re-run update — Find should then locate the existing account.
  5. For 429/5xx, retry after a delay; check Google Cloud status for IAM outages.
Defensive patterns

Strategy: retry

Validate before calling

id := strings.SplitN(email, "@", 2)[0]
if len(id) < 6 || len(id) > 30 || !regexp.MustCompile(`^[a-z][-a-z0-9]*[a-z0-9]$`).MatchString(id) {
    return fmt.Errorf("invalid service account id %q: 6-30 chars, [a-z][-a-z0-9]", id)
}
if n, _ := strconv.Atoi(strings.TrimSpace(os.Getenv("IAM_QUOTA_REMAINING"))); n < 1 { /* check roles/iam.serviceAccountAdmin first */ }

Type guard

func validAccountID(email string) bool {
    id := strings.SplitN(email, "@", 2)[0]
    return regexp.MustCompile(`^[a-z][-a-z0-9]{4,28}[a-z0-9]$`).MatchString(id)
}

Try / catch

if err := kopsUpdate(); err != nil {
    var gerr *googleapi.Error
    if errors.As(err, &gerr) {
        switch gerr.Code {
        case 409:
            log.Print("account exists; re-running update should Find it")
        case 403:
            log.Print("grant roles/iam.serviceAccountAdmin to the kOps identity")
        case 429, 500, 503:
            log.Print("transient; retry with backoff")
        }
    }
    return err
}

Prevention

When it happens

Trigger: Iam.ServiceAccounts().Create(ctx, "projects/<projectID>", req) returns non-nil err during `kops update cluster` for a non-shared ServiceAccount task.

Common situations: The service account name part of the email exceeds 30 chars or contains invalid characters; the kOps credentials lack iam.serviceAccounts.create; org policy (disableServiceAccountKeyCreation / constraints/iam.allowedPolicyMemberDomains) blocks it; the account was created concurrently (409 alreadyExists); transient 500/429 from GCP.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/ad7cf3ed16709e74. Report an issue: GitHub.