diff --git a/docs/dev-guide.md b/docs/dev-guide.md new file mode 100644 index 00000000..dd9563d5 --- /dev/null +++ b/docs/dev-guide.md @@ -0,0 +1,292 @@ +# Capsule Development Guide + +## Prerequisites + +### Tools + +Make sure you have these tools installed: + +- [Go 1.16+](https://golang.org/dl/) +- [OperatorSDK 1.7.2+](https://github.com/operator-framework/operator-sdk), or [Kubebuilder](https://github.com/kubernetes-sigs/kubebuilder) +- [KinD](https://github.com/kubernetes-sigs/kind), or [k3d](https://k3d.io/), with kubectl +- [ngrok](https://ngrok.com/) (if you want to run locally with remote Kubernetes) +- [golangci-lint](https://github.com/golangci/golangci-lint) +- OpenSSL + +### Kubernetes Cluster + +A lightweight Kubernetes within your laptop can be very handy for Kubernetes-native development like Capsule. + +#### By `k3d` + +```sh +# Install K3d cli by brew in Mac, or your preferred way +$ brew install k3d + +# Export your laptop's IP, e.g. retrieving it by: ifconfig +# Do change this IP to yours +$ export LAPTOP_HOST_IP=192.168.10.101 + +# Spin up a bare minimum cluster +# Refer to here for more options: https://k3d.io/v4.4.8/usage/commands/k3d_cluster_create/ +$ k3d cluster create k3s-capsule --servers 1 --agents 1 --no-lb --k3s-server-arg --tls-san=${K8S_API_IP} + +# This will create a cluster with 1 server and 1 worker node +$ kubectl get nodes +NAME STATUS ROLES AGE VERSION +k3d-k3s-capsule-server-0 Ready control-plane,master 2m13s v1.21.2+k3s1 +k3d-k3s-capsule-agent-0 Ready 2m3s v1.21.2+k3s1 + +# Or 2 Docker containers if you view it from Docker perspective +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +5c26ad840c62 rancher/k3s:v1.21.2-k3s1 "/bin/k3s agent" 53 seconds ago Up 45 seconds k3d-k3s-capsule-agent-0 +753998879b28 rancher/k3s:v1.21.2-k3s1 "/bin/k3s server --t…" 53 seconds ago Up 51 seconds 0.0.0.0:49708->6443/tcp k3d-k3s-capsule-server-0 +``` + +#### By `kind` + +```sh +# Prepare a kind config file with necessary customization +$ cat > kind.yaml < 56s v1.21.1 + +# Or 2 Docker containers if you view it from Docker perspective +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +7b329fd3a838 kindest/node:v1.21.1 "/usr/local/bin/entr…" About a minute ago Up About a minute 0.0.0.0:54894->6443/tcp kind-capsule-control-plane +7d50f1633555 kindest/node:v1.21.1 "/usr/local/bin/entr…" About a minute ago Up About a minute kind-capsule-worker +``` + +## Folk & clone the repository + +The `folk-clone-contribute-pr` flow is common for contributing to OSS projects like Kubernetes, Capsule. + +Let's assume you've folked it into your GitHub namespace, say `myuser`, and then you can clone it with Git protocol. +Do remember to change the `myuser` to yours. + +```sh +$ git clone git@github.com:myuser/capsule.git && cd capsule +``` + +It's a good practice to add the upsteam as the remote too so we can easily fetch and merge the upstream to our folk: + +```sh +$ git remote add upstream https://github.com/clastix/capsule.git +$ git remote -vv +origin git@github.com:myuser/capsule.git (fetch) +origin git@github.com:myuser/capsule.git (push) +upstream https://github.com/clastix/capsule.git (fetch) +upstream https://github.com/clastix/capsule.git (push) +``` + +## Build & deploy Capsule + +```sh +# Download the project dependencies +$ go mod download + +# Build the Capsule image +$ make docker-build + +# Retrieve the built image version +$ export CAPSULE_IMAGE_VESION=`docker images --format '{{.Tag}}' quay.io/clastix/capsule` + +# If k3s, load the image into cluster by +$ k3d image import --cluster k3s-capsule capsule quay.io/clastix/capsule:${CAPSULE_IMAGE_VESION} +# If Kind, load the image into cluster by +$ kind load docker-image --name kind-capsule quay.io/clastix/capsule:${CAPSULE_IMAGE_VESION} + +# deploy all the required manifests +# Note: 1) please retry if you saw errors; 2) if you want to clean it up first, run: make remove +$ make deploy + +# Make sure the controller is running +$ kubectl get pod -n capsule-system +NAME READY STATUS RESTARTS AGE +capsule-controller-manager-5c6b8445cf-566dc 1/1 Running 0 23s + +# Check the logs if needed +$ kubectl -n capsule-system logs --all-containers -l control-plane=controller-manager + +# You may have a try to deploy a Tenant too to make sure it works end to end +$ kubectl apply -f - < N.B.: Docker is a hard requirement since it's based on it - -According to your operative system and architecture, download the right binary -and place it on your `PATH`. - -Once done, you're ready to bootstrap in a glance of seconds, a fully functional -Kubernetes cluster. - -``` -# kind create cluster --name capsule -Creating cluster "capsule" ... - βœ“ Ensuring node image (kindest/node:v1.18.2) πŸ–Ό - βœ“ Preparing nodes πŸ“¦ - βœ“ Writing configuration πŸ“œ - βœ“ Starting control-plane πŸ•ΉοΈ - βœ“ Installing CNI πŸ”Œ - βœ“ Installing StorageClass πŸ’Ύ -Set kubectl context to "kind-capsule" -You can now use your cluster with: - -kubectl cluster-info --context kind-capsule - -Thanks for using kind! 😊 -``` - -The current `KUBECONFIG` will be populated with the `cluster-admin` -certificates and the context changed to the just born Kubernetes cluster. - -### Build the Docker image and push it to KinD -From the root path, issue the _make_ recipe: - -``` -# make docker-build -``` - -The image `quay.io/clastix/capsule:` will be available locally. Built image `` is resulting last one available [release](https://github.com/clastix/capsule/releases). - -Push it to _KinD_ with the following command: - -``` -# kind load docker-image --nodes capsule-control-plane --name capsule quay.io/clastix/capsule: -``` - -### Deploy the Kubernetes manifests -With the current `kind-capsule` context enabled, deploy all the required -manifests issuing the following command: - -``` -make deploy -``` - -This will install all the required Kubernetes resources, automatically. - -You can check if Capsule is running tailing the logs: - -``` -# kubectl -n capsule-system logs --all-containers -f -l control-plane=controller-manager -``` - -Since Capsule is built using _OperatorSDK_, logging is handled by the zap -module: log verbosity of the Capsule controller can be increased passing -the `--zap-log-level` option with a value from `1` to `10` or the -[basic keywords](https://godoc.org/go.uber.org/zap/zapcore#Level) although -it is suggested to use the `--zap-devel` flag to get also stack traces. - -> CA generation -> -> You could notice a restart of the Capsule pod upon installation, that's ok: -> Capsule is generating the CA and populating the Secret containing the TLS -> certificate to handle the webhooks and there's the need the reload the whole -> application to serve properly HTTPS requests. - -### Run Capsule locally -Debugging remote applications is always struggling but Operators just need -access to the Kubernetes API Server. - -#### Scaling down the remote Pod -First, ensure the Capsule pod is not running scaling down the Deployment. - -``` -# kubectl -n capsule-system scale deployment capsule-controller-manager --replicas=0 -deployment.apps/capsule-controller-manager scaled -``` - -> This is mandatory since Capsule uses Leader Election - -#### Providing TLS certificate for webhooks -The next step is to replicate the same environment Capsule is expecting in the Pod, -it means creating a fake certificate to handle HTTP requests. - -``` bash -mkdir -p /tmp/k8s-webhook-server/serving-certs -kubectl -n capsule-system get secret capsule-tls -o jsonpath='{.data.tls\.crt}' | base64 -d > /tmp/k8s-webhook-server/serving-certs/tls.crt -kubectl -n capsule-system get secret capsule-tls -o jsonpath='{.data.tls\.key}' | base64 -d > /tmp/k8s-webhook-server/serving-certs/tls.key -``` - -> We're using the certificates generate upon the first installation of Capsule: -> it means the Secret will be populated at the first start-up. -> If you plan to run it locally since the beginning, it means you will require -> to provide a self-signed certificate in the said directory. - -#### Starting NGROK -In another session, we need a `ngrok` session, mandatory to debug also webhooks -(YMMV). - -``` -# ngrok http https://localhost:9443 -ngrok by @inconshreveable - -Session Status online -Account Dario Tranchitella (Plan: Free) -Version 2.3.35 -Region United States (us) -Web Interface http://127.0.01:4040 -Forwarding http://cdb72b99348c.ngrok.io -> https://localhost:9443 -Forwarding https://cdb72b99348c.ngrok.io -> https://localhost:9443 -Connections ttl opn rt1 rt5 p50 p90 - 0 0 0.00 0.00 0.00 0.00 -``` - -What we need is the _ngrok_ URL (in this case, `https://cdb72b99348c.ngrok.io`) -since we're going to use this default URL as the `url` parameter for the -_Dynamic Admissions Control Webhooks_. - -#### Patching the MutatingWebhookConfiguration -Now it's time to patch the _MutatingWebhookConfiguration_ and the -_ValidatingWebhookConfiguration_ too, adding the said `ngrok` URL as base for -each defined webhook, as following: - -```diff -apiVersion: admissionregistration.k8s.io/v1 -kind: MutatingWebhookConfiguration -metadata: - - name: capsule-mutating-webhook-configuration -webhooks: - - name: owner.namespace.capsule.clastix.io - failurePolicy: Fail - rules: - - apiGroups: [""] - apiVersions: ["v1"] - operations: ["CREATE"] - resources: ["namespaces"] - clientConfig: -+ url: https://cdb72b99348c.ngrok.io/mutate-v1-namespace-owner-reference -- caBundle: -- service: -- namespace: system -- name: capsule -- path: /mutate-v1-namespace-owner-reference -... -``` - -#### Run Capsule -Finally, it's time to run locally Capsule using your preferred IDE (or not): -from the project root path, you can issue the following command. - -``` -make run -``` - -All the logs will start to flow in your standard output, feel free to attach -your debugger to set breakpoints as well! - -## Code convention -The changes must follow the Pull Request method where a _GitHub Action_ will -check the `golangci-lint`, so ensure your changes respect the coding standard. - -### golint -You can easily check them issuing the _Make_ recipe `golint`. - -``` -# make golint -golangci-lint run -c .golangci.yml -``` - -> Enabled linters and related options are defined in the [.golanci.yml file](../../.golangci.yml) - -### goimports -Also, the Go import statements must be sorted following the best practice: - -``` - - - - - -``` - -To help you out you can use the _Make_ recipe `goimports` - -``` -# make goimports -goimports -w -l -local "github.com/clastix/capsule" . -``` - -### Commits -All the Pull Requests must refer to an already open issue: this is the first phase to contribute also for informing maintainers about the issue. - -Commit's first line should not exceed 50 columns. - -A commit description is welcomed to explain more the changes: just ensure -to put a blank line and an arbitrary number of maximum 72 characters long -lines, at most one blank line between them. - -Please, split changes into several and documented small commits: this will help us to perform a better review. Commits must follow the Conventional Commits Specification, a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history; which makes it easier to write automated tools on top of. This convention dovetails with Semantic Versioning, by describing the features, fixes, and breaking changes made in commit messages. See [Conventional Commits Specification](https://www.conventionalcommits.org) to learn about Conventional Commits. - -> In case of errors or need of changes to previous commits, -> fix them squashing to make changes atomic. - -### Miscellanea -Please, add a new single line at end of any file as the current coding style.