# sample-apiserver: the reference implementation for a custom Kubernetes API

> The Kubernetes sample-apiserver is a working demonstration of the k8s.io/apiserver library, showing how to serve a custom resource type with its own storage, build, container image and deployment manifests, and to run it stand-alone against etcd with local client certificates. It is a staging repository synced from the main Kubernetes tree, so changes belong upstream.

**kubernetes/sample-apiserver** — Reference implementation of an apiserver for a custom Kubernetes API.

- Repository: https://github.com/kubernetes/sample-apiserver
- Stars: 603 · Forks: 223
- Language: Go
- License: Apache-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/kubernetes-sample-apiserver

## A staging mirror, not a place to send pull requests

The first thing at the top of the readme is a warning, and it changes how you should treat everything else in the repository.

It is an automatically published staged repository for Kubernetes. Contributions, including issues and pull requests, should be made to the main Kubernetes repository. The repository is read-only for importing and not used for direct contributions, and the CONTRIBUTING.md file has the details.

The mechanics are described in the Where does it come from section. sample-apiserver is synced from the staging directory of the main Kubernetes repository, code changes are made in that location, merged into kubernetes/kubernetes, and later synced here. So the canonical source is a directory inside the largest repository on GitHub, and this one is a mirror of it.

That is fine for how most people use it, which is as a reference implementation. The repository exists to show how the k8s.io/apiserver library is used to build a functional API server, and the readme notes that you should go-get or vendor it as k8s.io/sample-apiserver.

For anyone who wants to change it, the readme gives the recommended pattern: fork this repository, modify it to add your types, and then periodically rebase your changes on top of this repo, to pick up improvements and bug fixes to the apiserver. So the workflow is a fork plus a recurring rebase, not a pull request.

The compatibility rule is the reason the rebase is not optional. HEAD of this repo will match HEAD of k8s.io/apiserver, k8s.io/apimachinery and k8s.io/client-go, and the go.mod confirms it with pseudo-versions of all three stamped on the same day. A fork that drifts from those will not compile cleanly.

## Why CRDs are the option most people should pick

The readme spends its first recommendation on the project not being used at all, which is a rare and welcome thing to find in a reference implementation.

You may use this code if you want to build an Extension API Server to use with API Aggregation, or to build a stand-alone Kubernetes-style API server. However, consider two other options.

The first is CRDs. If you just want to add a resource to your Kubernetes cluster, consider using a Custom Resource Definition. They require less coding and rebasing, and the readme links the Kubernetes documentation on the differences between Custom Resource Definitions and Extension API Servers.

The second is apiserver-builder. If you want to build an Extension API server, consider using apiserver-builder instead of this repo, and it describes that project as a complete framework for generating the apiserver, client libraries and the installation program.

Both recommendations are about the same thing. An aggregated API server is a whole server, with its own storage, its own authentication and authorisation wiring, its own version negotiation, its own availability story and its own upgrade path. A CRD is a schema registered with the existing apiserver, and the extension runs inside the platform's lifecycle. If your requirement is a new kind of object that behaves like a Kubernetes resource, a CRD is almost always less machinery for the same outcome.

The cost of going the aggregated route is that you now own a service. The sample server the readme shows needs a CA, client certificates, an etcd and a kubeconfig, and in a cluster it needs an aggregation layer, a service registration and the RBAC to be trusted. That is a real operational commitment for a feature that a CRD would have delivered in a manifest.

## Getting the source and the code generator in place

Fetching the project is the part most people know, and the readme adds one wrinkle that trips people up.

```sh
git clone https://github.com/kubernetes/sample-apiserver
cd sample-apiserver
```

The wrinkle is the code generator. If you intend to generate code, you also need the code-generator repository to exist in an old-style location, and the suggested way to do that is to run go mod vendor to create and populate the vendor directory. That is because the code generation scripts expect the sibling repositories in a layout that predates Go modules, and vendoring reproduces that layout inside your checkout.

If you are already developing inside the main Kubernetes repository, following the community contributor guide, the problem does not exist. You already have a copy of this demo at kubernetes/staging/src/k8s.io/sample-apiserver, and its dependencies, including the code generator, are in usable locations.

That is a fair summary of the whole staging model in one paragraph. The demo lives in a directory that is part of a monorepo, and the monorepo arranges its sibling repositories in a way the generation scripts can find. Cloning the mirror alone loses that arrangement, which is why the vendor step is there.

The go.mod confirms the shape of the dependency set. The module is k8s.io/sample-apiserver, it requires Go 1.27, and its direct dependencies are the apiserver, apimachinery, client-go, code-generator, component-base, kube-openapi and utils, all as pseudo-versions, plus cobra for the command line, testify for tests, and the structured-merge-diff and randfill libraries. The indirect list is long, which is what pulling in a Kubernetes apiserver means.

## Adding your own types and regenerating

The workflow for making the server serve your own resource is the part that defines the pattern the readme wants you to copy.

If you change the API object type definitions in any of the pkg/apis/.../types.go files then you need to update the files generated from those definitions. The procedure is to create the vendor directory if necessary, and then invoke hack/update-codegen.sh with sample-apiserver as the current working directory. The script takes no arguments.

So the cycle is: edit the Go type definitions, run one script, and the deepcopy functions, the client, the listers, the informers and the apply configurations are all regenerated. That is the standard Kubernetes code generation model, and the reason the vendor directory is needed is that the generator walks a repository layout to find the API groups to generate for.

The point of the sample is that the type definition is the only file you hand write. Everything else in the API surface is generated, which is what makes it possible to have a new resource that behaves like a first class Kubernetes object, with proper list and watch semantics, rather than something that merely stores and returns JSON.

The top level structure of the repository shows the shape of the result. There is a main.go at the root, a pkg directory holding the API packages, an artifacts directory with the container image and example deployment manifests, a hack directory with the update scripts, a docs directory, and the usual repository furniture including an OWNERS file, a SECURITY_CONTACTS file and a code of conduct. The go.mod at the root is marked as a generated file that should not be edited directly.

## Building the binary and the image

The build is a single go build with the cross compilation settings written out explicitly, and the readme gives it as a plain block.

```
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o artifacts/simple-image/kube-sample-apiserver
```

CGO is disabled, which is what you want for a static binary that will run in a scratch style container, and the output path lands inside the artifacts directory that the container build then picks up.

The container image is built from that same directory and pushed to a registry of your choosing:

```
docker build -t MYPREFIX/kube-sample-apiserver:MYTAG ./artifacts/simple-image
docker push MYPREFIX/kube-sample-apiserver:MYTAG
```

The readme is careful to say that MYPREFIX and MYTAG should be replaced with something suitable, which is a reminder that this is a demonstration and the defaults are placeholders rather than a published image.

Deploying into a cluster then means editing the example manifest. You edit artifacts/example/deployment.yaml, updating the pod template's image reference to match what you pushed and setting the imagePullPolicy to something suitable, and then apply it:

```
kubectl apply -f artifacts/example
```

The imagePullPolicy detail is the kind of thing that bites people in local clusters, where the default of IfNotPresent combined with a mutable tag means you can be running an old image. The manifest ships as an example to be edited, which is the right shape for something that has to point at your own registry and your own namespace.

There is also a note on authentication plugins, which matters for a server you intend to run in a cluster. The normal build supports only a very sparse selection of authentication methods, while a much larger set is available in the client-go auth plugin directory. To support one of those, such as oidc, you add an import of the appropriate package to sample-apiserver/main.go:

```go
import _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
```

Or you can add support for all of them with a single import of the parent package. The fact that this is a one line change in a Go file is a small example of how the plugin loading works.

## Running it stand-alone without the aggregation layer

The stand-alone section is the most practically useful part of the readme, because it is how you develop against the server without a cluster's aggregator in the way.

During development it is helpful to run sample-apiserver stand-alone, without a Kubernetes API server for authn and authz and without aggregation. The readme is clear that this needs a couple of flags, keys and certs, and that you still need some kubeconfig, for example ~/.kube/config, but the Kubernetes cluster is not used for authentication or authorisation. A minikube or a local-up-cluster cluster will do.

Instead of trusting the aggregator inside kube-apiserver, the described setup uses local client certificate based X.509 authentication and authorisation. The client certificate is trusted by a CA and contains the group membership for the system:masters group, and since delegated authorisation is disabled with --authorization-skip-lookup, only that superuser group is authorised.

The steps are four, and they are ordinary openssl work. First a CA to sign the client certificate:

``` shell
   openssl req -nodes -new -x509 -keyout ca.key -out ca.crt
```

Then a client certificate for the user development in the system:masters group:

``` shell
   openssl req -out client.csr -new -newkey rsa:4096 -nodes -keyout client.key -subj "/CN=development/O=system:masters"
   openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -sha256 -out client.crt
```

Then, because curl wants client certificates in p12 format with a password, a conversion step:

``` shell
   openssl pkcs12 -export -in ./client.crt -inkey ./client.key -out client.p12 -passout pass:password
```

And finally the server itself, with etcd alongside it:

``` shell
   etcd &
   sample-apiserver --secure-port 8443 --etcd-servers http://127.0.0.1:2379 --v=7 \
      --client-ca-file ca.crt \
      --kubeconfig ~/.kube/config \
      --authentication-kubeconfig ~/.kube/config \
      --authorization-kubeconfig ~/.kube/config
```

The three kubeconfig flags have distinct roles, and the readme explains the first two: the plain kubeconfig is used for the shared informers to access Kubernetes resources, the one passed to --authentication-kubeconfig satisfies the delegated authenticator, and the third is for the delegated authorizer. Three kubeconfigs pointing at the same file is not a mistake, it is the shape of the stand-alone setup, and it is exactly the kind of detail that would take an afternoon to work out from the flags alone.

## Conclusion

Adopt sample-apiserver as a reference if you need an aggregated API server for a custom resource and want a working example of the storage, registration, code generation and deployment pieces rather than a library to depend on. Do not adopt it directly, because the repository is read-only, changes are made in the main Kubernetes tree, and the readme points at CRDs and apiserver-builder as the options to consider first. Before you start, decide honestly between a CustomResourceDefinition and an aggregated apiserver, and if you do fork it, plan to rebase periodically on upstream as the readme recommends.

## FAQ

### What is the Kubernetes sample-apiserver?

It is a demonstration of how to use the k8s.io/apiserver library to build a functional API server, showing the storage, registration, code generation, image build and deployment pieces needed for a custom resource type served as an aggregated API server.

### Should I use a CRD instead of an extension API server?

Probably. The readme says that if you just want to add a resource to your Kubernetes cluster, consider using a Custom Resource Definition, because they require less coding and rebasing. If you do want an aggregated API server, it points to apiserver-builder as a more complete framework than this repository.

### Where do I send changes to sample-apiserver?

To the main Kubernetes repository. This one is an automatically published staged mirror that is read-only for importing, and code changes are made at staging/src/k8s.io/sample-apiserver in kubernetes/kubernetes, then synced here. The recommended pattern for forking is to modify your types and periodically rebase on this repository.

### How do I add my own API types to sample-apiserver?

Edit the type definitions in the pkg/apis/.../types.go files, create the vendor directory if needed, and run hack/update-codegen.sh from the sample-apiserver directory. The script takes no arguments and regenerates the files derived from the type definitions.

### Can I run sample-apiserver without a Kubernetes cluster?

Yes. The readme describes a stand-alone mode with no aggregation and no cluster-based authn and authz, using a local CA and a client certificate for the system:masters group with delegated authorisation disabled, alongside an etcd instance. You still need a kubeconfig for the shared informers, but the cluster is not used for authentication.

## Sources

- [Issues](https://github.com/kubernetes/sample-apiserver/issues)
- [kubernetes/sample-apiserver on GitHub](https://github.com/kubernetes/sample-apiserver)
- [License: Apache-2.0](https://github.com/kubernetes/sample-apiserver/blob/master/LICENSE)
- [README](https://github.com/kubernetes/sample-apiserver/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kubernetes-sample-apiserver
