Open-Source Wikis

/

Istio

/

How to contribute

/

Patterns and conventions

istio/istio

Patterns and conventions

The unstated rules. Not all of these are written down anywhere else; most are inferred from the codebase. The official conventions doc is Development-Conventions on the GitHub wiki, and the performance-oriented Writing-Fast-and-Lean-Code is required reading before changing hot paths.

Go style

  • Follow Effective Go and the Go Code Review Comments. Repo-specific lints (tools/golangci-override.yaml) extend common/config/.golangci.yml.
  • Package names are lowercase and match their directory.
  • File names use lowercase with underscores when needed (push_context.go), never camelCase.
  • Type names use CamelCase: singular nouns for instances (Service, Endpoint), plural for collections (Endpoints).
  • Interface names commonly drop the I prefix and use -er (Generator, ConfigStore, ServiceDiscovery).
  • Errors propagate; do not log-and-return. Use fmt.Errorf("...: %w", err) to wrap. Sentinel errors live in the package they belong to.
  • panic is reserved for invariant violations. Use assert package or if cond { panic(...) } only behind -tags=assert.

Logging

Use pkg/log/, not the standard library logger and not zap directly. The pattern:

var log = istiolog.RegisterScope("foo", "what foo logs")

func bar() {
    log.WithLabels("proxy", proxyID).Debugf("processing %d items", n)
}

Rules:

  • Each subsystem has its own scope. Don't reuse default for new code.
  • Use Debugf/Infof/Warnf/Errorf with format strings; do not pre-format with Sprintf.
  • Attach structured labels with WithLabels(...) before the level call.
  • Errors and warnings should be actionable. Include enough context (proxy ID, resource name) that an operator can find the relevant resource.

Metrics

All Prometheus and OpenTelemetry metrics go through pkg/monitoring/. The pattern:

var pushTime = monitoring.NewSum(
    "pilot_xds_push_time_seconds",
    "Total time spent pushing xDS to proxies, by type",
    monitoring.WithLabels(typeLabel),
)

func init() { monitoring.MustRegister(pushTime) }

Naming:

  • Prefix with the component: pilot_*, citadel_*, istio_agent_*, istio_cni_*, istio_* for cross-cutting.
  • Use snake_case.
  • Histograms get _seconds, counters get _total (and the type Sum).

Do not register raw prometheus.* collectors; the wrapper handles dual emission to Prom and OTel and centralizes label registration.

Errors

  • Functions return error as the last return value.
  • Wrap errors at boundaries: return fmt.Errorf("listing services: %w", err).
  • Use errors.Is / errors.As rather than == for error comparisons.
  • multierror.Append (hashicorp/go-multierror) is the standard for collecting multiple errors during validation.

Configuration

Runtime knobs live in pilot/pkg/features/pilot.go (and similar features.go files in other components). The pattern:

var enableSomething = env.Register(
    "PILOT_ENABLE_SOMETHING",
    true,
    "When true, do the new thing",
).Get()

Rules:

  • Always go through pkg/env. Do not call os.Getenv directly.
  • Names are SCREAMING_SNAKE_CASE with a component prefix.
  • Provide a defaulted value and a one-line description; CI checks that all env vars are documented (make envvarlinter).
  • Default to the safe behavior. Most new features start with PILOT_ENABLE_*=false and flip to true after stability.

User-facing config lives in:

  • MeshConfig (mesh-wide; istio/api)
  • IstioOperator (install-time; istio/api plus operator/pkg/apis/)
  • Helm values.yaml (per chart; manifests/charts/<chart>/values.yaml)
  • Per-CRD APIs (Istio networking, security, telemetry)

Controllers: krt vs informers

The codebase has two controller styles. Both are accepted; use the one that fits.

  • pkg/kube/krt/ — declarative Collection + Fetch. New code, especially anything that touches Ambient, uses krt. The framework manages event delivery, dependency tracking, and re-computation. Tests are easier (state is just a function of inputs).
  • Hand-written informer + queuepkg/kube/kclient/, pkg/queue/. Older code, including most sidecar-mode networking, uses this style. It is fine to extend; do not rewrite working code just to migrate.

A useful rule: if your controller computes derived data from 3+ sources, krt will save you time. If it does one thing in response to one event, an informer + queue is simpler.

Generated code

Several types of code are generated:

  • Protobufs under pkg/config/xds/ and other places, regenerated via make proto.
  • CRD manifests under manifests/charts/base/files/, regenerated via make update-crds.
  • Golden test outputs under testdata/, regenerated via make refresh-goldens.
  • License manifests under licenses/, regenerated via make mirror-licenses.

Generated files include a Code generated by ... DO NOT EDIT. header. Do not hand-edit them. CI gates on make gen-check — if the output diffs against committed files, the PR fails.

Performance hot paths

The xDS push path and the cache are performance-critical. Conventions:

  • Pre-allocate slices and maps with capacity hints when the size is knowable.
  • Prefer values over pointers when the value fits in a cache line; the existing networking code does this throughout.
  • Reuse buffers via sync.Pool for objects that are allocated per request.
  • Do not spawn goroutines per request. The xDS server uses a fixed worker pool (pilot/pkg/xds/pushqueue.go) and the push pipeline is single-writer per stream.
  • Cache encoded protobuf.Any outputs keyed by their inputs (see pilot/pkg/networking/core/cluster_cache.go). Caching with the wrong key has caused CVEs in the past, so the cache asserts on key collisions when -tags=assert is set.

The performance guide on the wiki lists more (memory pooling, allocation profiling). Always benchmark before and after a hot-path change.

Tests

  • Co-locate tests: foo.gofoo_test.go.
  • Table-driven tests for translation and validation logic. Each case has a name field and is wrapped in t.Run(tc.name, ...).
  • Goldens for outputs that are unwieldy to assert in code. Regenerate with make refresh-goldens and inspect the diff.
  • Avoid time.Sleep in tests; use assert.EventuallyEqual or framework retries.
  • Do not depend on package-level mutable state across tests. The xDS code in particular has historically had this kind of leak; new tests should t.Cleanup everything they create.

Comments

  • Public APIs have GoDoc comments. The first sentence repeats the symbol name.
  • Internal helpers can be terse but should explain why if the what is non-obvious.
  • TODOs include a username or issue link: // TODO(@howardjohn): switch to delta xDS once #1234 lands.

Every Go, shell, and YAML file starts with the Apache 2.0 header:

// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// ...

make precommit adds it for you. The linter rejects PRs without it.

Imports

Goimports groups: standard library, third party, istio.io/.... The common/scripts/run_goimports.sh and gofumpt enforce this.

import (
    "context"
    "fmt"

    "github.com/spf13/cobra"
    "google.golang.org/grpc"

    "istio.io/api/mesh/v1alpha1"
    "istio.io/istio/pkg/log"
)

File / directory rules

  • New top-level packages should have a 1-2 line doc comment in doc.go or the package's primary file.
  • Test fixtures under testdata/ are not formatted by gofmt and not bound by the same lint rules.
  • Files generated by make gen belong with the source they generate; do not put generated files under testdata/.

When in doubt

Read the surrounding code and match its style. Istio's conventions are dense but consistent: a function in pilot/pkg/networking/core/ and one in pkg/kube/krt/ look the same way. If your PR introduces a stylistic outlier, expect a reviewer to point it out.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – Istio wiki | Factory