diff --git a/docs/book/SUMMARY.md b/docs/book/SUMMARY.md deleted file mode 100644 index 4271c113e75c..000000000000 --- a/docs/book/SUMMARY.md +++ /dev/null @@ -1,30 +0,0 @@ -# Managing Kubernetes Infrastructure with the Cluster API - -* [Introduction](README.md) - -## Getting Started - -* [Abbreviations](ABBREVIATIONS.md) -* [Glossary](GLOSSARY.md) -* [Interoperability](getting_started/interoperability.md) -* [Existing Providers](getting_started/existing_providers.md) - -## Common Code - -* [Architecture](common_code/architecture.md) -* [Repository Layout](common_code/repository_layout.md) -* [Cluster Controller](common_code/cluster_controller.md) -* [Machine Controller](common_code/machine_controller.md) -* [MachineSet Controller](common_code/machineset_controller.md) -* [MachineDeployment Controller](common_code/machinedeployment_controller.md) - -## Creating a New Provider - -* [Overview](provider_implementations/overview.md) -* [Naming](provider_implementations/naming.md) -* [Generate CRDs](provider_implementations/generate_crds.md) -* [Register Schemes](provider_implementations/register_schemes.md) -* [Create Actuators](provider_implementations/create_actuators.md) -* [Register Controllers](provider_implementations/register_controllers.md) -* [Building, Running, and Testing](provider_implementations/building_running_and_testing.md) - diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index 3fb709448ec6..74f20a8e54af 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -19,7 +19,12 @@ - [Provider Implementers](./providers/implementers.md) - [v1alpha1 to v1alpha2](./providers/v1alpha1-to-v1alpha2.md) - [Cluster Infrastructure](./providers/cluster-infrastructure.md) - + - [Implementer's Guide](./providers/implementers-guide/overview.md) + - [Naming](./providers/implementers-guide/naming.md) + - [Create Repo and Generate CRDs](./providers/implementers-guide/generate_crds.md) + - [Create API](./providers/implementers-guide/create_api.md) + - [Controllers and Reconciliation](./providers/implementers-guide/controllers_and_reconciliation.md) + - [Building, Running, Testing](./providers/implementers-guide/building_running_and_testing.md) - [Reference](./reference/reference.md) - [Glossary](./reference/glossary.md) - [Provider List](./reference/providers.md) diff --git a/docs/book/src/providers/implementers-guide/building_running_and_testing.md b/docs/book/src/providers/implementers-guide/building_running_and_testing.md new file mode 100644 index 000000000000..039a629d16b1 --- /dev/null +++ b/docs/book/src/providers/implementers-guide/building_running_and_testing.md @@ -0,0 +1,132 @@ +# Building, Running, Testing + +## Docker Image Name + +The patch in `config/default/manager_image_patch.yaml` will be applied to the manager pod. +Right now there is a placeholder `IMAGE_URL`, which you will need to change to your actual image. + +### Development Images +It's likely that you will want one location and tag for release development, and another during development. + +The approach most Cluster API projects is using [a `Makefile` that uses `sed` to replace the image URL][sed] on demand during development. + +[sed]: https://github.com/kubernetes-sigs/cluster-api/blob/e0fb83a839b2755b14fbefbe6f93db9a58c76952/Makefile#L201-L204 + +## Deployment + +### cert-manager + +Cluster API uses [cert-manager] to manage the certificates it needs for its webhooks. +Before you apply Cluster API's yaml, you should [install `cert-manager`][cm-install] + +[cert-manager]: https://github.com/jetstack/cert-manager +[cm-install]: https://docs.cert-manager.io/en/latest/getting-started/install/kubernetes.html + +``` +kubectl apply -f https://github.com/jetstack/cert-manager/releases/download//cert-manager.yaml +``` + +### Cluster API + +Before you can deploy the infrastructure controller, you'll need to deploy Cluster API itself. + +You can [use a precompiled manifest][install], or clone [`cluster-api`][capi] and apply its manifests using `kustomize`. + +``` shell +cd cluster-api +kustomize build config/default | kubectl apply -f- +``` + +Check the status of the manager to make sure it's running properly + +```shell +$ kubectl describe -n capi-system pod | grep -A 5 Conditions +Conditions: + Type Status + Initialized True + Ready True + ContainersReady True + PodScheduled True +``` + + +[install]: https://cluster-api.sigs.k8s.io/tasks/installation.html#install-cluster-api + +### Your provider + +Now you can apply your provider as well: + +``` +$ cd cluster-api-provider-mailgun +$ kustomize build config/default | envsubst | kubectl apply -f- +$ kubectl describe -n cluster-api-provider-mailgun-system pod | grep -A 5 Conditions +Conditions: + Type Status + Initialized True + Ready True + ContainersReady True + PodScheduled True +``` + +### Tiltfile +Cluster API development requires a lot of iteration, and the "build, tag, push, update deployment" workflow can be very tedious. +[Tilt](https://tilt.dev) makes this process much simpler by watching for updates, then automatically building and deploying them. + +You can visit [some example repositories][capidev], but you can get started by writing out a yaml manifest and using the following [`Tiltfile`][tiltfile] +`kustomize build config/default | envsubst > capm.yaml` + +[capidev]: https://github.com/chuckha/capi-dev +[tiltfile]: https://docs.tilt.dev/tiltfile_concepts.html + +```starlark +allow_k8s_contexts('kubernetes-admin@kind) + +k8s_yaml('capm.yaml') + +docker_build('/cluster-api-controller-mailgun-amd64', '.') +``` + +You can then use Tilt to watch the logs coming off your container + + +## Your first Cluster + +Let's try our cluster out. We'll make some simple YAML: + +```yaml +apiVersion: cluster.x-k8s.io/v1alpha2 +kind: Cluster +metadata: + name: hello-mailgun +spec: + clusterNetwork: + pods: + cidrBlocks: ["192.168.0.0/16"] + infrastructureRef: + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3 + kind: MailgunCluster + name: hello-mailgun +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3 +kind: MailgunCluster +metadata: + name: hello-mailgun +spec: + priority: "ExtremelyUrgent" + request: "Please make me a cluster, with sugar on top?" + requester: "cluster-admin@example.com" +``` + +We apply it as normal with `kubectl apply -f .yaml`. + +If all goes well, you should be getting an email to the address you configured when you set up your management cluster: + +![An email from mailgun urgently requesting a cluster](cluster-email.png) + +## Conclusion + +Obviously, this is only the first step. +We need to implement our Machine object too, and log events, handle updates, and many more things. + +Hopefully you feel empowered to go out and create your own provider now. +The world is your Kubernetes-based oyster! diff --git a/docs/book/src/providers/implementers-guide/cluster-email.png b/docs/book/src/providers/implementers-guide/cluster-email.png new file mode 100644 index 000000000000..896d3b5afb93 Binary files /dev/null and b/docs/book/src/providers/implementers-guide/cluster-email.png differ diff --git a/docs/book/src/providers/implementers-guide/configure.md b/docs/book/src/providers/implementers-guide/configure.md new file mode 100644 index 000000000000..200ce065b722 --- /dev/null +++ b/docs/book/src/providers/implementers-guide/configure.md @@ -0,0 +1,159 @@ +# Configure + +## YAML + +`kubebuilder` generates most of the YAML you'll need to deploy a container. +We just need to modify it to add our new secrets. + +First, let's add our secret as a [patch] to the manager yaml. + +`config/default/manager_config.yaml`: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + env: + - name: MAILGUN_API_KEY + valueFrom: + secretKeyRef: + name: mailgun-secret + key: api_key + - name: MAILGUN_DOMAIN + valueFrom: + configMapKeyRef: + name: mailgun-config + key: mailgun_domain + - name: MAIL_RECIPIENT + valueFrom: + configMapKeyRef: + name: mailgun-config + key: mail_recipient +``` + +And then, we have to add that patch to [`config/default/kustomization.yaml`][kustomizeyaml]: + +```yaml +patchesStrategicMerge +- manager_image_patch.yaml +# Protect the /metrics endpoint by putting it behind auth. +# Only one of manager_auth_proxy_patch.yaml and +# manager_prometheus_metrics_patch.yaml should be enabled. +- manager_auth_proxy_patch.yaml +# If you want your controller-manager to expose the /metrics +# endpoint w/o any authn/z, uncomment the following line and +# comment manager_auth_proxy_patch.yaml. +# Only one of manager_auth_proxy_patch.yaml and +# manager_prometheus_metrics_patch.yaml should be enabled. +- manager_config.yaml +``` + +[kustomizeyaml]: https://github.com/kubernetes-sigs/kustomize/blob/master/docs/glossary.md#kustomization +[patch]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md + +## Our configuration + +There's many ways to manage configuration in production. +The convention many Cluster-API projects use is enviroment variables. + +`config/manager/configuration.yaml` + +```yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: mailgun-config + namespace: system +type: Opaque +stringData: + api_key: ${MAILGUN_API_KEY} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: mailgun-config + namespace: system +data: + mailgun_domain: ${MAILGUN_DOMAIN} + mail_recipient: ${MAILGUN_RECIPIENT} +``` + +And add this to `config/manager/kustomization.yaml` + +```yaml +resources: +- manager.yaml +- credentials.yaml +``` + +You can now (hopefully) generate your yaml! + +``` +kustomize build config/default +``` + +## RBAC Role + +The default [RBAC role][role] contains permissions for accessing your cluster infrastructure CRDs, but not for accessing Cluster API resources. +You'll need to add these to `config/rbac/role.yaml` + +[role]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ + +```diff +diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml +index e9352ce..29008db 100644 +--- a/config/rbac/role.yaml ++++ b/config/rbac/role.yaml +@@ -6,6 +6,24 @@ metadata: + creationTimestamp: null + name: manager-role + rules: ++- apiGroups: ++ - cluster.x-k8s.io ++ resources: ++ - clusters ++ - clusters/status ++ verbs: ++ - get ++ - list ++ - watch ++- apiGroups: ++ - cluster.x-k8s.io ++ resources: ++ - machines ++ - machines/status ++ verbs: ++ - get ++ - list ++ - watch + - apiGroups: + - infrastructure.cluster.x-k8s.io + resources: +``` + +## EnvSubst + +_A tool like [direnv](https://direnv.net/) can be used to help manage enviroment variables._ + + +`kustomize` does not handle replacing those `${VARIABLES}` with actual values. +For that, we use [`envsubst`][envsubst]. + +You'll need to have those environment variables (`MAILGUN_API_KEY`, `MAILGUN_DOMAIN`, `MAILGUN_RECIPIENT`) in your environment when you generate the final yaml file. + +Then we call envsubst in line, like so: + +``` +kustomize build config/default | envsubst +``` + +[envsubst]: https://linux.die.net/man/1/envsubst + diff --git a/docs/book/src/providers/implementers-guide/controllers_and_reconciliation.md b/docs/book/src/providers/implementers-guide/controllers_and_reconciliation.md new file mode 100644 index 000000000000..91015ee55e19 --- /dev/null +++ b/docs/book/src/providers/implementers-guide/controllers_and_reconciliation.md @@ -0,0 +1,268 @@ +# Controllers and Reconciliation + +From the [kubebuilder book][controller]: + +> Controllers are the core of Kubernetes, and of any operator. +> +> It’s a controller’s job to ensure that, for any given object, the actual state of the world (both the cluster state, and potentially external state like running containers for Kubelet or loadbalancers for a cloud provider) matches the desired state in the object. +> Each controller focuses on one root Kind, but may interact with other Kinds. +> +> We call this process reconciling. + +[controller]: https://book.kubebuilder.io/cronjob-tutorial/controller-overview.html#whats-in-a-controller + +Right now, we can create objects in our API but we won't do anything about it. Let's fix that. + +# Let's see the Code + +Kubebuilder has created our first controller in `controllers/mailguncluster_controller.go`. Let's take a look at what got generated: + +```go +// MailgunClusterReconciler reconciles a MailgunCluster object +type MailgunClusterReconciler struct { + client.Client + Log logr.Logger +} + +// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=mailgunclusters,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=mailgunclusters/status,verbs=get;update;patch + +func (r *MailgunClusterReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + _ = context.Background() + _ = r.Log.WithValues("mailguncluster", req.NamespacedName) + + // your logic here + + return ctrl.Result{}, nil +} +``` + +## RBAC Roles + +Those `// +kubebuilder...` lines tell kubebuilder to generate [RBAC] roles so the manager we're writing can access its own managed resources. +We also need to add roles that will let it retrieve (but not modify) Cluster API objects. +So we'll add another annotation for that: + +``` +// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=mailgunclusters,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=mailgunclusters/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status,verbs=get;list;watch +``` + +Make sure to add the annotation to both `MailgunClusterReconciler` and `MailgunMachineReconciler`. + +Regenerate the RBAC roles after you are done: + +``` +make manifests +``` + + +[RBAC]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole + +## State + +Let's focus on that `struct` first. +First, a word of warning: no guarantees are made about parallel access, both on one machine or multiple machines. +That means you should not store any important state in memory: if you need it, write it into a Kubernetes object and store it. + +We're going to be sending mail, so let's add a few extra fields: + +```go +// MailgunClusterReconciler reconciles a MailgunCluster object +type MailgunClusterReconciler struct { + client.Client + Log logr.Logger + Mailgun mailgun.Mailgun + Recipient string +} +``` + +## Reconciliation + +Now it's time for our Reconcile function. +Reconcile is only passed a name, not an object, so let's retrieve ours. + +Here's a naive example: + +``` +func (r *MailgunClusterReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + ctx := context.Background() + _ = r.Log.WithValues("mailguncluster", req.NamespacedName) + + var cluster infrastructurev1alpha3.MailgunCluster + if err := r.Get(ctx, req.NamespacedName, &cluster); err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} +``` + +By returning an error, we request that our controller will get `Reconcile()` called again. +That may not always be what we want - what if the object's been deleted? So let's check that: + +``` +var cluster infrastructurev1alpha3.MailgunCluster +if err := r.Get(ctx, req.NamespacedName, &cluster); err != nil { + // import apierrors "k8s.io/apimachinery/pkg/api/errors" + if apierrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err +} +``` + +Now, if this were any old `kubebuilder` project we'd be done, but in our case we have one more object to retrieve. +Cluster API splits a cluster into two objects: the [`Cluster` defined by Cluster API itself][cluster]. +We'll want to retrieve that as well. +Luckily, cluster API [provides a helper for us][getowner]. + +```go +cluster, err := util.GetOwnerCluster(ctx, r.Client, &mg) +if err != nil { + return ctrl.Result{}, err + +} +``` + +### client-go versions +At the time this document was written, `kubebuilder` pulls `client-go` version `1.14.1` into `go.mod` (it looks like `k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible`). + +If you encounter an error when compiling like: + +``` +../pkg/mod/k8s.io/client-go@v11.0.1-0.20190409021438-1a26190bd76a+incompatible/rest/request.go:598:31: not enough arguments in call to watch.NewStreamWatcher + have (*versioned.Decoder) + want (watch.Decoder, watch.Reporter)` +``` + +You may need to bump `client-go`. At time of writing, that means `1.15`, which looks like: `k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible`. +## The fun part + +_More Documentation: [The Kubebuilder Book][book] has some excellent documentation on many things, including [how to write good controllers!][implement]_ + +[book]: https://book.kubebuilder.io/ +[implement]: https://book.kubebuilder.io/cronjob-tutorial/controller-implementation.html + +Now that we have our objects, it's time to do something with them! +This is where your provider really comes into it's own. +In our case, let's try sending some mail: + +```go +subject := fmt.Sprintf("[%s] New Cluster %s requested", mgCluster.Spec.Priority, cluster.Name) +body := fmt.Sprint("Hello! One cluster please.\n\n%s\n", mgCluster.Spec.Request) + +msg := mailgun.NewMessage(mgCluster.Spec.Requester, subject, body, r.Recipient) +_, _, err = r.Mailgun.Send(msg) +if err != nil { + return ctrl.Result{}, err +} +``` + +## Idempotency + +But wait, this isn't quite right. +`Reconcile()` gets called periodically for updates, and any time any updates are made. +That would mean we're potentially sending an email every few minutes! +This is an important thing about controllers: they need to be [*idempotent*][idempotent]. + +So in our case, we'll store the result of sending a message, and then check to see if we've sent one before. + +``` +if mgCluster.Status.MessageID != nil { + // We already sent a message, so skip reconcilation + return ctrl.Result{}, nil +} + +subject := fmt.Sprintf("[%s] New Cluster %s requested", mgCluster.Spec.Priority, cluster.Name) +body := fmt.Sprintf("Hello! One cluster please.\n\n%s\n", mgCluster.Spec.Request) + +msg := mailgun.NewMessage(mgCluster.Spec.Requester, subject, body, r.Recipient) +_, msgID, err := r.Mailgun.Send(msg) +if err != nil { + return ctrl.Result{}, err +} + +// patch from sigs.k8s.io/cluster-api/util/patch +helper, err := patch.NewHelper(&mgCluster, r.Client) +if err != nil { + return ctrl.Result{}, err +} +mgCluster.Status.MessageID = &msgID +if err := helper.Patch(ctx, &mgCluster); err != nil { + return ctrl.Result{}, errors.Wrapf(err, "couldn't patch cluster %q", mgCluster.Name) +} + +return ctrl.Result{}, nil +``` + +[cluster]: https://godoc.org/sigs.k8s.io/cluster-api/api/v1alpha3#Cluster +[getowner]: https://godoc.org/sigs.k8s.io/cluster-api/util#GetOwnerMachine +[idempotent]: https://stackoverflow.com/questions/1077412/what-is-an-idempotent-operation + +#### A note about the status + +Usually, the `Status` field should only be fields that can be _computed from existing state_. +Things like whether a machine is running can be retrieved from an API, and cluster status can be queried by a healthcheck. +The message ID is ephemeral, so it should properly go in the `Spec` part of the object. +Anything that can't be recreated, either with some sort of deterministic generation method or by querying/observing actual state, needs to be in Spec. +This is to support proper disaster recovery of resources. +If you have a backup of your cluster and you want to restore it, Kubernetes doesn't let you restore both spec & status together. + +We use the MessageID as a `Status` here to illustrate how one might issue status updates in a real application. + + +## Update `main.go` with your new fields + +If you added fields to your reconciler, you'll need to update `main.go`. + +Right now, it probably looks like this: + +```go +if err = (&controllers.MailgunClusterReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("MailgunCluster"), +}).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "MailgunCluster") + os.Exit(1) +} +``` + +Let's add our configuration. +We're going to use environment variables for this: + +```go +domain := os.Getenv("MAILGUN_DOMAIN") +if domain == "" { + setupLog.Info("missing required env MAILGUN_DOMAIN") + os.Exit(1) +} + +apiKey := os.Getenv("MAILGUN_API_KEY") +if apiKey == "" { + setupLog.Info("missing required env MAILGUN_API_KEY") + os.Exit(1) +} + +recipient := os.Getenv("MAIL_RECIPIENT") +if recipient == "" { + setupLog.Info("missing required env MAIL_RECIPIENT") + os.Exit(1) +} + +mg := mailgun.NewMailgun(domain, apiKey) + +if err = (&controllers.MailgunClusterReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("MailgunCluster"), + Mailgun: mg, + Recipient: recipient, +}).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "MailgunCluster") + os.Exit(1) +} +``` + +If you have some other state, you'll want to initialize it here! + diff --git a/docs/book/src/providers/implementers-guide/create_api.md b/docs/book/src/providers/implementers-guide/create_api.md new file mode 100644 index 000000000000..37b9cc4276ea --- /dev/null +++ b/docs/book/src/providers/implementers-guide/create_api.md @@ -0,0 +1,65 @@ +# Defining your API + +The API generated by Kubebuilder is just a shell, your actual API will likely have more fields defined on it. + +Kubernetes has a lot of conventions and requirements around API design. +The [Kubebuilder docs][apidesign] have some helpful hints on how to design your types. + +[apidesign]: https://book.kubebuilder.io/cronjob-tutorial/api-design.html#designing-an-api + + +Let's take a look at what was generated for us: + +```go +// MailgunClusterSpec defines the desired state of MailgunCluster +type MailgunClusterSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +// MailgunClusterStatus defines the observed state of MailgunCluster +type MailgunClusterStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} +``` + +Our API is based on Mailgun, so we're going to have some email based fields: + +```go +type Priority string + +const ( + // PriorityUrgent means do this right away + PriorityUrgent = Priority("Urgent") + + // PriorityUrgent means do this immediately + PriorityExtremelyUrgent = Priority("ExtremelyUrgent") + + // PriorityBusinessCritical means you absolutely need to do this now + PriorityBusinessCritical = Priority("BusinessCritical") +) + +// MailgunClusterSpec defines the desired state of MailgunCluster +type MailgunClusterSpec struct { + // Priority is how quickly you need this cluster + Priority Priority `json:"priority"` + // Request is where you ask extra nicely + Request string `json:"request"` + // Requester is the email of the person sending the request + Requester string +} + +// MailgunClusterStatus defines the observed state of MailgunCluster +type MailgunClusterStatus struct { + // MessageID is set to the message ID from Mailgun when our message has been sent + MessageID *string `json:"response"` +} +``` + +As the deleted comments request, run `make manager manifests` to regenerate some of the generated data files afterwards. + +```bash +git add . +git commit -m "Added cluster types" +``` diff --git a/docs/book/src/providers/implementers-guide/generate_crds.md b/docs/book/src/providers/implementers-guide/generate_crds.md new file mode 100644 index 000000000000..85a22e65b050 --- /dev/null +++ b/docs/book/src/providers/implementers-guide/generate_crds.md @@ -0,0 +1,87 @@ +### Create a repository + +```bash +mkdir cluster-api-provider-mailgun +cd src/sigs.k8s.io/cluster-api-provider-mailgun +git init +``` + +You'll then need to set up [go modules][gomod] + +```bash +$ go mod init github.com/liztio/cluster-api-provider-mailgun +go: creating new go.mod: module github.com/liztio/cluster-api-provider-mailgun +``` + +### Generate scaffolding + +```bash +kubebuilder init --domain cluster.x-k8s.io +``` + +`kubebuilder init` will create the basic repository layout, including a simple containerized manager. +It will also initialize the external go libraries that will be required to build your project. + +Commit your changes so far: + +```bash +git commit -m "Generate scaffolding." +``` +### Generate provider resources for Clusters and Machines + +Here you will be asked if you want to generate resources and controllers. +You'll want both of them: + +```bash +kubebuilder create api --group infrastructure --version v1alpha3 --kind MailgunCluster +kubebuilder create api --group infrastructure --version v1alpha3 --kind MailgunMachine +``` + +``` +Create Resource under pkg/apis [y/n]? +y +Create Controller under pkg/controller [y/n]? +y +``` + + +### Add Status subresource + +The [status subresource][status] lets Spec and Status requests for custom resources be addressed seperately so requests don't conflict with each other. +It also lets you split RBAC rules between Spec and Status. +It's stable in Kubernetes as of [v1.16][rbac], but you will have to [manually enable it in Kubebuilder][kbstatus]. + +Add the `subresource:status` annotation to your `cluster_types.go` `machine_types.go` + +```go +// +kubebuilder:subresource:status +// +kubebuilder:object:root=true + +// MailgunCluster is the Schema for the mailgunclusters API +type MailgunCluster struct { +``` + +```go +// +kubebuilder:subresource:status +// +kubebuilder:object:root=true + +// MailgunMachine is the Schema for the mailgunmachines API +type MailgunMachine struct { +``` + +And regenerate the CRDs: +```shell +make manifests +``` + +[status]: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#status-subresource +[rbac]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.16/#customresourcesubresources-v1beta1-apiextensions-k8s-io +[kbstatus]: https://book.kubebuilder.io/reference/generating-crd.html?highlight=status#status + + +### Commit your changes + +```bash +git add . +git commit -m "Generate Cluster and Machine resources." +``` diff --git a/docs/book/src/providers/implementers-guide/naming.md b/docs/book/src/providers/implementers-guide/naming.md new file mode 100644 index 000000000000..dbf513e76b06 --- /dev/null +++ b/docs/book/src/providers/implementers-guide/naming.md @@ -0,0 +1,54 @@ +# Repository Naming + +The naming convention for new Cluster API [_provider_ repositories][repo-naming] +is generally of the form `cluster-api-provider-${env}`, where `${env}` is a, +possibly short, name for the _environment_ in question. For example +`cluster-api-provider-gcp` is an implementation for the Google Cloud Platform, +and `cluster-api-provider-aws` is one for Amazon Web Services. Note that an +environment may refer to a cloud, bare metal, virtual machines, or any other +infrastructure hosting Kubernetes. Finally, a single environment may include +more than one [_variant_][variant-naming]. So for example, +`cluster-api-provider-aws` may include both an implementation based on EC2 as +well as one based on their hosted EKS solution. + + +## A note on Acronyms + +Because these names end up being so long, developers of Cluster API frequently refer to providers by acronyms. +Cluster API itself becomes [CAPI], pronounced "Cappy." +cluster-api-provider-aws is [CAPA], pronounced "KappA." +cluster-api-provider-gcp is [CAPG], pronounced "Cap Gee," [and so on][letterc]. + +[CAPI]: https://cluster-api.sigs.k8s.io/reference/glossary.html#capi +[CAPA]: https://cluster-api.sigs.k8s.io/reference/glossary.html#capa +[CAPG]: https://cluster-api.sigs.k8s.io/reference/glossary.html#capg +[letterc]: https://cluster-api.sigs.k8s.io/reference/glossary.html#c + +# Resource Naming + +For the purposes of this guide we will create a provider for a +service named **mailgun**. Therefore the name of the repository will be +`cluster-api-provider-mailgun`. + +Every Kubernetes resource has a *Group*, *Version* and *Kind* that uniquely +identifies it. + +* The resource *Group* is similar to package in a language. + It disambiguates different APIs that may happen to have identically named *Kind*s. + Groups often contain a domain name, such as k8s.io. + The domain for Cluster API resources is `cluster.x-k8s.io`, and infrastructure providers generally use `infrastructure.cluster.x-k8s.io`. +* The resource *Version* defines the stability of the API and its backward compatibility guarantees. + Examples include v1alpha1, v1beta1, v1, etc. and are governed by the Kubernetes API Deprecation Policy [^1]. + Your provider should expect to abide by the same policies. +* The resource *Kind* is the name of the objects we'll be creating and modifying. + In this case it's `MailgunMachine` and `MailgunCluster`. + +For example, our cluster object will be: +```yaml +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3 +kind: MailgunCluster +``` +[repo-naming]: https://github.com/kubernetes-sigs/cluster-api/issues/383 +[variant-naming]: https://github.com/kubernetes-sigs/cluster-api/issues/480 + +[^1]: https://kubernetes.io/docs/reference/using-api/deprecation-policy/ diff --git a/docs/book/src/providers/implementers-guide/overview.md b/docs/book/src/providers/implementers-guide/overview.md new file mode 100644 index 000000000000..8d489ed7f660 --- /dev/null +++ b/docs/book/src/providers/implementers-guide/overview.md @@ -0,0 +1,70 @@ +# Overview + +In order to demonstrate how to develop a new Cluster API provider we will use +`kubebuilder` to create an example provider. For more information on `kubebuilder` +and CRDs in general we highly recommend reading the [Kubebuilder Book][kubebuilder-book]. +Much of the information here was adapted directly from it. + +This is an _infrastructure_ provider - tasked with managing provider-specific resources for clusters and machines. +There are also [bootstrap providers][bootstrap], which turn machines into Kubernetes nodes. + +[bootstrap]: https://cluster-api.sigs.k8s.io/reference/providers.html?highlight=bootstrap#bootstrap + +## Prerequisites + +- Install [`kubectl`][kubectl-install] +- Install [`kustomize`][install-kustomize] +- Install [`kubebuilder`][install-kubebuilder] + +### tl;dr + +{{#tabs name:"kubectl and kustomize" tabs:"MacOS,Linux"}} +{{#tab MacOS}} + +```bash +# Install kubectl +brew install kubernetes-cli + +# Install kustomize +brew install kustomize +``` +{{#/tab }} +{{#tab Linux}} + +```bash +# Install kubectl +KUBECTL_VERSION=$(curl -sf https://storage.googleapis.com/kubernetes-release/release/stable.txt) +curl -fLO https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl + +# Install kustomize +OS_TYPE=linux +curl -sf https://api.github.com/repos/kubernetes-sigs/kustomize/releases/latest |\ + grep browser_download |\ + grep ${OS_TYPE} |\ + cut -d '"' -f 4 |\ + xargs curl -f -O -L +mv kustomize_*_${OS_TYPE}_amd64 /usr/local/bin/kustomize +chmod u+x /usr/local/bin/kustomize +``` + +{{#/tab }} +{{#/tabs }} + +``` +# Install Kubebuilder +os=$(go env GOOS) +arch=$(go env GOARCH) + +# download kubebuilder and extract it to tmp +curl -sL https://go.kubebuilder.io/dl/2.1.0/${os}/${arch} | tar -xz -C /tmp/ + +# move to a long-term location and put it on your path +# (you'll need to set the KUBEBUILDER_ASSETS env var if you put it somewhere else) +sudo mv /tmp/kubebuilder_2.1.0_${os}_${arch} /usr/local/kubebuilder +export PATH=$PATH:/usr/local/kubebuilder/bin +``` + +[kubebuilder-book]: https://book.kubebuilder.io/ +[kubectl-install]: http://kubernetes.io/docs/user-guide/prereqs/ +[install-kustomize]: https://github.com/kubernetes-sigs/kustomize/blob/master/docs/INSTALL.md +[install-kubebuilder]: https://book.kubebuilder.io/quick-start.html#installation