Skip to content

Commit 4431f3e

Browse files
authored
✨ Add Tilt setup (#16)
Adding Tilt setup for local development Signed-off-by: janiskemper <[email protected]>
1 parent 37d6f44 commit 4431f3e

File tree

9 files changed

+444
-31
lines changed

9 files changed

+444
-31
lines changed

.envrc.sample

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export KUBECONFIG=$PWD/.mgt-cluster-kubeconfig.yaml
2+
export K8S_VERSION=1-27
3+
export GIT_PROVIDER_B64=Z2l0aHVi
4+
export GIT_ACCESS_TOKEN_B64=mybase64encodedtoken
5+
export GIT_ORG_NAME_B64=U292ZXJlaWduQ2xvdWRTdGFjaw==
6+
export GIT_REPOSITORY_NAME_B64=Y2x1c3Rlci1zdGFja3M=
7+
export EXP_CLUSTER_RESOURCE_SET=true
8+
export EXP_MACHINE_POOL=true
9+
export CLUSTER_TOPOLOGY=true
10+
export EXP_RUNTIME_SDK=true
11+
export EXP_MACHINE_SET_PREFLIGHT_CHECKS=true
12+
export CLUSTER_NAME=test-dfkhje

Makefile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ kustomize: $(KUSTOMIZE) ## Build a local copy of kustomize
9595
$(KUSTOMIZE): # Build kustomize from tools folder.
9696
go install sigs.k8s.io/kustomize/kustomize/[email protected]
9797

98+
TILT := $(abspath $(TOOLS_BIN_DIR)/tilt)
99+
tilt: $(TILT) ## Build a local copy of tilt
100+
$(TILT):
101+
@mkdir -p $(TOOLS_BIN_DIR)
102+
MINIMUM_TILT_VERSION=0.33.3 hack/ensure-tilt.sh
103+
98104
ENVSUBST := $(abspath $(TOOLS_BIN_DIR)/envsubst)
99105
envsubst: $(ENVSUBST) ## Build a local copy of envsubst
100106
$(ENVSUBST): # Build envsubst from tools folder.
@@ -105,6 +111,11 @@ setup-envtest: $(SETUP_ENVTEST) ## Build a local copy of setup-envtest
105111
$(SETUP_ENVTEST): # Build setup-envtest from tools folder.
106112
go install sigs.k8s.io/controller-runtime/tools/[email protected]
107113

114+
CTLPTL := $(abspath $(TOOLS_BIN_DIR)/ctlptl)
115+
ctlptl: $(CTLPTL) ## Build a local copy of ctlptl
116+
$(CTLPTL):
117+
go install github.com/tilt-dev/ctlptl/cmd/[email protected]
118+
108119
CLUSTERCTL := $(abspath $(TOOLS_BIN_DIR)/clusterctl)
109120
clusterctl: $(CLUSTERCTL) ## Build a local copy of clusterctl
110121
$(CLUSTERCTL):
@@ -466,3 +477,10 @@ modules: generate-modules ## Update go.mod & go.sum
466477
.PHONY: builder-image-push
467478
builder-image-push: ## Build $(CONTROLLER_SHORT)-builder to a new version. For more information see README.
468479
BUILDER_IMAGE=$(BUILDER_IMAGE) ./hack/upgrade-builder-image.sh
480+
481+
create-workload-cluster-docker: $(ENVSUBST) $(KUBECTL)
482+
cat .cluster.yaml | $(ENVSUBST) - | $(KUBECTL) apply -f -
483+
484+
.PHONY: tilt-up
485+
tilt-up: env-vars-for-wl-cluster $(ENVSUBST) $(KUBECTL) $(KUSTOMIZE) $(TILT) cluster ## Start a mgt-cluster & Tilt. Installs the CRDs and deploys the controllers
486+
EXP_CLUSTER_RESOURCE_SET=true $(TILT) up --port=10351

README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,46 @@ The first two are handled by this operator here. The node images, on the other h
2222

2323
## Implementing a provider integration
2424

25-
Further information and documentation on how to implement a provider integration will follow soon.
25+
Further information and documentation on how to implement a provider integration will follow soon.
26+
27+
## Developing Cluster Stack Operator
28+
29+
Developing our operator is quite easy. First, you need to install some base requirements: Docker and Go. Second, you need to configure your environment variables. Then you can start developing with the local Kind cluster and the Tilt UI to create a workload cluster that is already pre-configured.
30+
31+
## Setting Tilt up
32+
1. Install Docker and Go. We expect you to run on a Linux OS.
33+
2. Create an ```.envrc``` file and specify the values you need. See the .envrc.sample for details.
34+
35+
## Developing with Tilt
36+
37+
<p align="center">
38+
<img alt="tilt" src="./docs/pics/tilt.png" width=800px/>
39+
</p>
40+
41+
Operator development requires a lot of iteration, and the “build, tag, push, update deployment” workflow can be very tedious. Tilt makes this process much simpler by watching for updates and automatically building and deploying them. To build a kind cluster and to start Tilt, run:
42+
43+
```shell
44+
make tilt-up
45+
```
46+
> To access the Tilt UI please go to: `http://localhost:10350`
47+
48+
49+
You should make sure that everything in the UI looks green. If not, e.g. if the clusterstack has not been synced, you can trigger the Tilt workflow again. In case of the clusterstack button this might be necessary, as it cannot be applied right after startup of the cluster and fails. Tilt unfortunately does not include a waiting period.
50+
51+
If everything is green, then you can already check for your clusterstack that has been deployed. You can use a tool like k9s to have a look at the management cluster and its custom resources.
52+
53+
In case your clusterstack shows that it is ready, you can deploy a workload cluster. This could be done through the Tilt UI, by pressing the button in the top right corner "Create Workload Cluster". This triggers the `make create-workload-cluster-docker`, which uses the environment variables and the cluster-template.
54+
55+
In case you want to change some code, you can do so and see that Tilt triggers on save. It will update the container of the operator automatically.
56+
57+
If you want to change something in your ClusterStack or Cluster custom resources, you can have a look at `.cluster.yaml` and `.clusterstack.yaml`, which Tilt uses.
58+
59+
To tear down the workload cluster press the "Delete Workload Cluster" button. After a few minutes the resources should be deleted.
60+
61+
To tear down the kind cluster, use:
62+
63+
```shell
64+
$ make delete-bootstrap-cluster
65+
```
66+
67+
If you have any trouble finding the right command, then you can use `make help` to get a list of all available make targets.

Tiltfile

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
# -*- mode: Python -*-
2+
load("ext://uibutton", "cmd_button", "location", 'text_input')
3+
load("ext://restart_process", "docker_build_with_restart")
4+
5+
kustomize_cmd = "./hack/tools/bin/kustomize"
6+
envsubst_cmd = "./hack/tools/bin/envsubst"
7+
sed_cmd = "sed 's/:=\"\"//g'"
8+
tools_bin = "./hack/tools/bin"
9+
10+
#Add tools to path
11+
os.putenv("PATH", os.getenv("PATH") + ":" + tools_bin)
12+
13+
update_settings(k8s_upsert_timeout_secs = 60) # on first tilt up, often can take longer than 30 seconds
14+
15+
16+
settings = {
17+
"allowed_contexts": [
18+
"kind-cso",
19+
],
20+
"deploy_cert_manager": True,
21+
"preload_images_for_kind": True,
22+
"kind_cluster_name": "cso",
23+
"capi_version": "v1.5.2",
24+
"cert_manager_version": "v1.11.0",
25+
"kustomize_substitutions": {
26+
},
27+
}
28+
29+
# global settings
30+
settings.update(read_json(
31+
"tilt-settings.json",
32+
default = {},
33+
))
34+
35+
if settings.get("trigger_mode") == "manual":
36+
trigger_mode(TRIGGER_MODE_MANUAL)
37+
38+
if "allowed_contexts" in settings:
39+
allow_k8s_contexts(settings.get("allowed_contexts"))
40+
41+
if "default_registry" in settings:
42+
default_registry(settings.get("default_registry"))
43+
44+
# deploy CAPI
45+
def deploy_capi():
46+
version = settings.get("capi_version")
47+
capi_uri = "https://github.com/kubernetes-sigs/cluster-api/releases/download/{}/cluster-api-components.yaml".format(version)
48+
cmd = "curl -sSL {} | {} | kubectl apply -f -".format(capi_uri, envsubst_cmd)
49+
local(cmd, quiet = True)
50+
if settings.get("extra_args"):
51+
extra_args = settings.get("extra_args")
52+
if extra_args.get("core"):
53+
core_extra_args = extra_args.get("core")
54+
if core_extra_args:
55+
for namespace in ["capi-system", "capi-webhook-system"]:
56+
patch_args_with_extra_args(namespace, "capi-controller-manager", core_extra_args)
57+
if extra_args.get("kubeadm-bootstrap"):
58+
kb_extra_args = extra_args.get("kubeadm-bootstrap")
59+
if kb_extra_args:
60+
patch_args_with_extra_args("capi-kubeadm-bootstrap-system", "capi-kubeadm-bootstrap-controller-manager", kb_extra_args)
61+
62+
def deploy_capd():
63+
version = settings.get("capi_version")
64+
capd_uri = "https://github.com/kubernetes-sigs/cluster-api/releases/download/{}/infrastructure-components-development.yaml".format(version)
65+
cmd = "curl -sSL {} | {} | kubectl apply -f -".format(capd_uri, envsubst_cmd)
66+
local(cmd, quiet = True)
67+
68+
69+
def prepare_environment():
70+
local("kubectl create namespace cluster --dry-run=client -o yaml | kubectl apply -f -")
71+
72+
# if it's already present then don't copy
73+
if not os.path.exists('.clusterstack.yaml'):
74+
local("cp config/cso/clusterstack.yaml .clusterstack.yaml")
75+
76+
k8s_yaml('.clusterstack.yaml')
77+
78+
if not os.path.exists('.cluster.yaml'):
79+
local("cp config/cso/cluster.yaml .cluster.yaml")
80+
81+
def patch_args_with_extra_args(namespace, name, extra_args):
82+
args_str = str(local("kubectl get deployments {} -n {} -o jsonpath='{{.spec.template.spec.containers[0].args}}'".format(name, namespace)))
83+
args_to_add = [arg for arg in extra_args if arg not in args_str]
84+
if args_to_add:
85+
args = args_str[1:-1].split()
86+
args.extend(args_to_add)
87+
patch = [{
88+
"op": "replace",
89+
"path": "/spec/template/spec/containers/0/args",
90+
"value": args,
91+
}]
92+
local("kubectl patch deployment {} -n {} --type json -p='{}'".format(name, namespace, str(encode_json(patch)).replace("\n", "")))
93+
94+
# Users may define their own Tilt customizations in tilt.d. This directory is excluded from git and these files will
95+
# not be checked in to version control.
96+
def include_user_tilt_files():
97+
user_tiltfiles = listdir("tilt.d")
98+
for f in user_tiltfiles:
99+
include(f)
100+
101+
def append_arg_for_container_in_deployment(yaml_stream, name, namespace, contains_image_name, args):
102+
for item in yaml_stream:
103+
if item["kind"] == "Deployment" and item.get("metadata").get("name") == name and item.get("metadata").get("namespace") == namespace:
104+
containers = item.get("spec").get("template").get("spec").get("containers")
105+
for container in containers:
106+
if contains_image_name in container.get("name"):
107+
container.get("args").extend(args)
108+
109+
def fixup_yaml_empty_arrays(yaml_str):
110+
yaml_str = yaml_str.replace("conditions: null", "conditions: []")
111+
return yaml_str.replace("storedVersions: null", "storedVersions: []")
112+
113+
## This should have the same versions as the Dockerfile
114+
tilt_dockerfile_header_cso = """
115+
FROM docker.io/alpine/helm:3.12.2 as helm
116+
117+
FROM docker.io/library/alpine:3.18.0 as tilt
118+
WORKDIR /
119+
COPY --from=helm --chown=root:root --chmod=755 /usr/bin/helm /usr/local/bin/helm
120+
COPY manager .
121+
"""
122+
123+
# Build CSO and add feature gates
124+
def deploy_cso():
125+
# yaml = str(kustomizesub("./hack/observability")) # build an observable kind deployment by default
126+
yaml = str(kustomizesub("./config/default"))
127+
local_resource(
128+
name = "cso-components",
129+
cmd = ["sh", "-ec", sed_cmd, yaml, "|", envsubst_cmd],
130+
labels = ["CSO"],
131+
)
132+
133+
# Forge the build command
134+
ldflags = "-extldflags \"-static\" " + str(local("hack/version.sh")).rstrip("\n")
135+
build_env = "CGO_ENABLED=0 GOOS=linux GOARCH=amd64"
136+
build_cmd = "{build_env} go build -ldflags '{ldflags}' -o .tiltbuild/manager cmd/main.go".format(
137+
build_env = build_env,
138+
ldflags = ldflags,
139+
)
140+
# Set up a local_resource build of the provider's manager binary.
141+
local_resource(
142+
"cso-manager",
143+
cmd = "mkdir -p .tiltbuild; " + build_cmd,
144+
deps = ["api", "cmd", "config", "internal", "vendor", "pkg", "go.mod", "go.sum"],
145+
labels = ["CSO"],
146+
)
147+
148+
entrypoint = ["/manager"]
149+
extra_args = settings.get("extra_args")
150+
if extra_args:
151+
entrypoint.extend(extra_args)
152+
153+
# Set up an image build for the provider. The live update configuration syncs the output from the local_resource
154+
# build into the container.
155+
docker_build_with_restart(
156+
ref = "ghcr.io/sovereigncloudstack/cso-staging",
157+
context = "./.tiltbuild/",
158+
dockerfile_contents = tilt_dockerfile_header_cso,
159+
target = "tilt",
160+
entrypoint = entrypoint,
161+
only = "manager",
162+
live_update = [
163+
sync(".tiltbuild/manager", "/manager"),
164+
],
165+
ignore = ["templates"],
166+
)
167+
k8s_yaml(blob(yaml))
168+
k8s_resource(workload = "cso-controller-manager", labels = ["CSO"])
169+
k8s_resource(
170+
objects = [
171+
"cso-system:namespace",
172+
"clusterstackreleases.clusterstack.x-k8s.io:customresourcedefinition",
173+
"clusterstacks.clusterstack.x-k8s.io:customresourcedefinition",
174+
"cso-controller-manager:serviceaccount",
175+
"cso-leader-election-role:role",
176+
"cso-manager-role:clusterrole",
177+
"cso-leader-election-rolebinding:rolebinding",
178+
"cso-manager-rolebinding:clusterrolebinding",
179+
"cso-serving-cert:certificate",
180+
"cso-cluster-stack-variables:secret",
181+
"cso-selfsigned-issuer:issuer",
182+
#"cso-validating-webhook-configuration:validatingwebhookconfiguration",
183+
],
184+
new_name = "cso-misc",
185+
labels = ["CSO"],
186+
)
187+
188+
def clusterstack():
189+
k8s_resource(objects = ["clusterstack:clusterstack"], new_name = "clusterstack", labels = ["CLUSTERSTACK"])
190+
191+
def base64_encode(to_encode):
192+
encode_blob = local("echo '{}' | tr -d '\n' | base64 - | tr -d '\n'".format(to_encode), quiet = True)
193+
return str(encode_blob)
194+
195+
def base64_encode_file(path_to_encode):
196+
encode_blob = local("cat {} | tr -d '\n' | base64 - | tr -d '\n'".format(path_to_encode), quiet = True)
197+
return str(encode_blob)
198+
199+
def read_file_from_path(path_to_read):
200+
str_blob = local("cat {} | tr -d '\n'".format(path_to_read), quiet = True)
201+
return str(str_blob)
202+
203+
def base64_decode(to_decode):
204+
decode_blob = local("echo '{}' | base64 --decode -".format(to_decode), quiet = True)
205+
return str(decode_blob)
206+
207+
def ensure_envsubst():
208+
if not os.path.exists(envsubst_cmd):
209+
local("make {}".format(os.path.abspath(envsubst_cmd)))
210+
211+
def ensure_kustomize():
212+
if not os.path.exists(kustomize_cmd):
213+
local("make {}".format(os.path.abspath(kustomize_cmd)))
214+
215+
def kustomizesub(folder):
216+
yaml = local("hack/kustomize-sub.sh {}".format(folder), quiet = True)
217+
return yaml
218+
219+
def waitforsystem():
220+
local("kubectl wait --for=condition=ready --timeout=300s pod --all -n capi-kubeadm-bootstrap-system")
221+
local("kubectl wait --for=condition=ready --timeout=300s pod --all -n capi-kubeadm-control-plane-system")
222+
local("kubectl wait --for=condition=ready --timeout=300s pod --all -n capi-system")
223+
224+
def deploy_observability():
225+
k8s_yaml(blob(str(local("{} build {}".format(kustomize_cmd, "./hack/observability/"), quiet = True))))
226+
227+
k8s_resource(workload = "promtail", extra_pod_selectors = [{"app": "promtail"}], labels = ["observability"])
228+
k8s_resource(workload = "loki", extra_pod_selectors = [{"app": "loki"}], labels = ["observability"])
229+
k8s_resource(workload = "grafana", port_forwards = "3000", extra_pod_selectors = [{"app": "grafana"}], labels = ["observability"])
230+
231+
##############################
232+
# Actual work happens here
233+
##############################
234+
ensure_envsubst()
235+
ensure_kustomize()
236+
237+
include_user_tilt_files()
238+
239+
load("ext://cert_manager", "deploy_cert_manager")
240+
241+
if settings.get("deploy_cert_manager"):
242+
deploy_cert_manager()
243+
244+
if settings.get("deploy_observability"):
245+
deploy_observability()
246+
247+
deploy_capi()
248+
249+
deploy_capd()
250+
251+
deploy_cso()
252+
253+
clusterstack()
254+
255+
waitforsystem()
256+
257+
prepare_environment()
258+
259+
## TODO
260+
cmd_button(
261+
"create workload cluster",
262+
argv=["make", "create-workload-cluster-docker"],
263+
location=location.NAV,
264+
icon_name="add_circle",
265+
)

config/cso/cluster.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
apiVersion: cluster.x-k8s.io/v1beta1
2+
kind: Cluster
3+
metadata:
4+
name: "${CLUSTER_NAME}"
5+
namespace: ${NAMESPACE}
6+
spec:
7+
clusterNetwork:
8+
services:
9+
cidrBlocks: ["10.128.0.0/12"]
10+
pods:
11+
cidrBlocks: ["192.168.0.0/16"]
12+
serviceDomain: "cluster.local"
13+
topology:
14+
class: docker-ferrol-1-27-v1
15+
controlPlane:
16+
metadata: {}
17+
replicas: 1
18+
variables:
19+
- name: imageRepository
20+
value: ""
21+
version: v1.27.3
22+
workers:
23+
machineDeployments:
24+
- class: workeramd64
25+
name: md-0
26+
replicas: 1

0 commit comments

Comments
 (0)