Skip to content

Commit 8bb4101

Browse files
author
Huy Mai
committed
Add Metal3 Fake API Server (FKAS)
Signed-off-by: Huy Mai <[email protected]>
1 parent 80b6ce5 commit 8bb4101

File tree

14 files changed

+1837
-4
lines changed

14 files changed

+1837
-4
lines changed

.github/dependabot.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ updates:
2020
- "/"
2121
- "/api"
2222
- "/hack/tools"
23+
- "/hack/fake-apiserver"
2324
- "/test"
2425
schedule:
2526
interval: "weekly"
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: build-fkas-images-action
2+
3+
on:
4+
push:
5+
branches:
6+
- 'main'
7+
paths:
8+
- 'hack/fake-apiserver/**'
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
build_FKAS:
15+
name: Build Metal3-FKAS image
16+
if: github.repository == 'metal3-io/cluster-api-provider-metal3'
17+
uses: metal3-io/project-infra/.github/workflows/container-image-build.yml@main
18+
with:
19+
image-name: "metal3-fkas"
20+
pushImage: true
21+
dockerfile-directory: hack/fake-apiserver
22+
secrets:
23+
QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }}
24+
QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }}
25+
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

Makefile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ APIS_DIR := api
4040
TEST_DIR := test
4141
BIN_DIR := bin
4242
TOOLS_BIN_DIR := $(abspath $(TOOLS_DIR)/$(BIN_DIR))
43+
FAKE_APISERVER_DIR := hack/fake-apiserver
4344

4445
# Set --output-base for conversion-gen if we are not within GOPATH
4546
ifneq ($(abspath $(ROOT_DIR)),$(shell $(GO) env GOPATH)/src/github.com/metal3-io/cluster-api-provider-metal3)
@@ -349,6 +350,8 @@ modules: ## Runs go mod to ensure proper vendoring.
349350
cd $(APIS_DIR) && $(GO) mod verify
350351
cd $(TEST_DIR) && $(GO) mod tidy
351352
cd $(TEST_DIR) && $(GO) mod verify
353+
cd $(FAKE_APISERVER_DIR) && $(GO) mod tidy
354+
cd $(FAKE_APISERVER_DIR) && $(GO) mod verify
352355

353356
.PHONY: generate
354357
generate: ## Generate code
@@ -449,6 +452,14 @@ docker-build: ## Build the docker image for controller-manager
449452
docker-push: ## Push the docker image
450453
docker push $(CONTROLLER_IMG)-$(ARCH):$(TAG)
451454

455+
.PHONY: build-fkas
456+
# Allow overriding this by setting CONTAINER_RUNTIME var
457+
CONTAINER_RUNTIME := $(if $(CONTAINER_RUNTIME),$(CONTAINER_RUNTIME),docker)
458+
export CONTAINER_RUNTIME
459+
460+
build-fkas:
461+
cd $(FAKE_APISERVER_DIR) && $(CONTAINER_RUNTIME) build --build-arg ARCH=$(ARCH) -t "quay.io/metal3-io/metal3-fkas:latest" .
462+
452463
## --------------------------------------
453464
## Docker — All ARCH
454465
## --------------------------------------
@@ -656,7 +667,8 @@ verify-boilerplate:
656667

657668
.PHONY: verify-modules
658669
verify-modules: modules
659-
@if !(git diff --quiet HEAD -- go.sum go.mod hack/tools/go.mod hack/tools/go.sum); then \
670+
@if !(git diff --quiet HEAD -- go.sum go.mod hack/tools/go.mod hack/tools/go.sum \
671+
hack/fake-apiserver/go.mod hack/fake-apiserver/go.sum); then \
660672
echo "go module files are out of date"; exit 1; \
661673
fi
662674

docs/releasing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ This triggers two things:
9898

9999
We also need to create one or more tags for the Go modules ecosystem:
100100

101-
- For any subdirectory with `go.mod` in it (excluding `hack/tools`), create
102-
another Git tag with directory prefix, ie.
101+
- For any subdirectory with `go.mod` in it (excluding `hack/tools` and
102+
`hack/fake-apiserver`), create another Git tag with directory prefix, ie.
103103
`git tag api/v1.x.y` and `git tag test/v1.x.y`. This enables the
104104
tags to be used as a Go module version for any downstream users.
105105

hack/fake-apiserver/Dockerfile

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright 2024 The Kubernetes Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Support FROM override
16+
ARG BUILD_IMAGE=docker.io/golang:1.22.7@sha256:192683db8982323952988c7b86c098ee7ecc6cbeb202bf7c113ff9be5358367c
17+
ARG BASE_IMAGE=gcr.io/distroless/static:nonroot@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f
18+
19+
# Build the fkas binary on golang image
20+
FROM $BUILD_IMAGE AS base
21+
WORKDIR /workspace
22+
23+
# Run this with docker build --build_arg $(go env GOPROXY) to override the goproxy
24+
ARG goproxy=https://proxy.golang.org
25+
ENV GOPROXY=$goproxy
26+
27+
# Copy the Go Modules manifests
28+
COPY go.mod go.sum ./
29+
30+
# Cache deps before building and copying source so that we don't need to re-download as much
31+
# and so that source changes don't invalidate our downloaded layer
32+
RUN go mod download
33+
34+
# Build Fkas
35+
FROM base AS build-fkas
36+
37+
# Copy the sources
38+
COPY cmd/metal3-fkas/*.go ./
39+
40+
# Build
41+
ARG ARCH=amd64
42+
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH} \
43+
go build -a -ldflags '-extldflags "-static"' \
44+
-o fkas .
45+
46+
# Build fkas-reconciler
47+
FROM base AS build-fkas-reconciler
48+
49+
# Copy the sources
50+
COPY cmd/metal3-fkas-reconciler/*.go ./
51+
52+
# Build
53+
ARG ARCH=amd64
54+
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH} \
55+
go build -a -ldflags '-extldflags "-static"' \
56+
-o reconciler .
57+
58+
# Copy the controller-manager into a thin image
59+
FROM $BASE_IMAGE
60+
WORKDIR /
61+
# Use uid of nonroot user (65532) because kubernetes expects numeric user when applying pod security policies
62+
COPY --from=build-fkas /workspace/fkas .
63+
COPY --from=build-fkas-reconciler /workspace/reconciler .
64+
USER 65532
65+
ENTRYPOINT ["/fkas"]

hack/fake-apiserver/README.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# Metal3 Fake Kubernetes API server system (Metal3-FKAS)
2+
3+
## FKAS
4+
5+
Metal3-FKAS tool for testing CAPI-related projects.
6+
When being asked, it generates new fake kubernetes api endpoint, that responds
7+
to all typical requests that CAPI sends to a newly provisioned cluster.
8+
9+
Despite being developed for Metal3 ecosystem, FKAS is provider-free. It can be adopted
10+
and used by any CAPI provider with intention to test the provider provisioning ability,
11+
without using real nodes.
12+
13+
### Purpose
14+
15+
After a CAPI Infrastructure provisions a new cluster, CAPI will send queries
16+
towards the newly launched cluster's API server to verify that the cluster is
17+
fully up and running.
18+
19+
In a simulated provisioning process, there are no real nodes, hence we cannot
20+
have an actual API server running inside the node. Booting up a real kubelet
21+
and etcd servers elsewhere is possible, but these processes are likely to consume
22+
a lot of resources.
23+
24+
FKAS is useful in this situation. When a request is sent towards `/register` endpoint,
25+
it will spawn a new simulated kubernetes API server with *unique* a host and
26+
port pair.
27+
User can, then, inject the address into cluster template consumed by
28+
CAPI with any infra provider.
29+
30+
### How to use
31+
32+
You can build the `metal3-fkas` image that is suitable for
33+
your local environment with
34+
35+
```shell
36+
make build-fkas
37+
```
38+
39+
The result is an image with label `quay.io/metal3-io/metal3-fkas:<your-arch-name>`
40+
41+
Alternatively, you can also build a custom image with
42+
43+
```shell
44+
cd hack/fake-apiserver
45+
docker build -t <custom tag> .
46+
```
47+
48+
For local tests, it's normally needed to load the image into the cluster.
49+
For e.g. with `minikube`
50+
51+
```shell
52+
minikube image load quay.io/metal3-io/metal3-fkas:latest
53+
```
54+
55+
Now you can deploy this container to the cluster, for e.g. with the deployment
56+
if k8s/metal3-fkas.yaml
57+
58+
```shell
59+
kubectl apply -f metal3-fkas.yaml
60+
```
61+
62+
After building the container image and deploy it to the bootstrap kubernetes cluster,
63+
you need to create a tunnel to send request to it and get response, by using
64+
a LoadBalancer, or a simple port-forward
65+
66+
```shell
67+
fkas_pod_name=$(kubectl get pods -n default -l app=metal3-fkas-system -o jsonpath='{.items[0].metadata.name}')
68+
kubectl port-forward pod/${fkas_pod_name} 3333:3333 2>/dev/null&
69+
```
70+
71+
Now, you can generate a fake API server endpoint by sending
72+
a POST request to the fake API server.
73+
74+
```shell
75+
namespace=<cluster-namespace>
76+
cluster_name=<cluster-name>
77+
78+
cluster_endpoint=$(curl -X POST "localhost:3333/register" \
79+
-H "Content-Type: application/json" -d '{
80+
"cluster": "'$cluster_name'",
81+
"namespace": "'$namespace'"
82+
}')
83+
```
84+
85+
The fake API server will return a response with the ip and port of the newly
86+
generated api server. For example:
87+
88+
```json
89+
{
90+
"Resource": "metal3/test1",
91+
"Host": "10.244.0.83",
92+
"Port": 20000
93+
}
94+
```
95+
96+
A new cluster can be provisioned by injecting the host and port we
97+
got from FKAS to the cluster template provided by a CAPI infrastructure
98+
provider. For e.g., with CAPM3 that can be done as followed:
99+
100+
```shell
101+
host=$(echo ${cluster_endpoints} | jq -r ".Host")
102+
port=$(echo ${cluster_endpoints} | jq -r ".Port")
103+
104+
# Injecting the new api address into the cluster template by
105+
# exporting these env vars
106+
export CLUSTER_APIENDPOINT_HOST="${host}"
107+
export CLUSTER_APIENDPOINT_PORT="${port}"
108+
109+
clusterctl generate cluster "${cluster}" \
110+
--from "${CLUSTER_TEMPLATE}" \
111+
--target-namespace "${namespace}" > /tmp/${cluster}-cluster.yaml
112+
kubectl apply -f /tmp/${cluster}-cluster.yaml
113+
```
114+
115+
After the cluster is created, CAPI will expect that information like node name
116+
and provider ID is registered in the API server. Since our API server doesn't
117+
live inside the node, we will need to feed the info to it, by sending a
118+
PUT request to `/updateNode` endpoint:
119+
120+
```shell
121+
curl -X PUT "localhost:3333/updateNode" -H "Content-Type: application/json" -d '{
122+
"cluster": "<cluster-name>",
123+
"namespace": "<namespace>",
124+
"nodeName": "<machine-object-name>",
125+
"providerID": "<provider-id>",
126+
"uuid": "<node-uuid>",
127+
"labels": "<node-labels>",
128+
"k8sversion": "<k8s-version-of-workload-cluster>"
129+
}'
130+
```
131+
132+
### Acknowledgements
133+
134+
This was developed thanks to the implementation of
135+
[Cluster API Provider In Memory (CAPIM)](https://github.com/kubernetes-sigs/cluster-api/tree/main/test/infrastructure/inmemory).
136+
137+
## Metal3 FKAS System
138+
139+
### FKAS in Metal3
140+
141+
In metal3 ecosystem, currently we have two ways of simulating a workflow without
142+
using any baremetal or virtual machines:
143+
144+
- [FakeIPA container](https://github.com/metal3-io/utility-images/tree/main/fake-ipa)
145+
- BMO simulation mode
146+
147+
In both of these cases, the "nodes" are not able to boot up any kubernetes api server,
148+
hence the needs of having mock API servers on-demands.
149+
150+
Similar to the general case, after having BMHs provisioned to `available` state,
151+
the user can send a request towards the Fake API server endpoint `/register`,
152+
which will spawn a new API server, with an unique `Host` and `Port` pair.
153+
154+
User can, then, use this IP address to feed the cluster template, by exporting
155+
`CLUSTER_APIENDPOINT_HOST` and `CLUSTER_APIENDPOINT_PORT` variables.
156+
157+
There is no need of manually check and send node info to `/updateNode`, as we have
158+
another tool to automate that part.
159+
160+
### Metal3-FKAS-Reconciler
161+
162+
This tool runs as a side-car container alongside FKAS, and works specifically
163+
for Metal3. It eliminates the needs of user to manually fetch the nodes information
164+
and send to `/updateNode` (as described earlier), by constantly watch the changes
165+
in BMH objects, notice if a BMH is being provisioned to a kubernetes node, and
166+
send request to `/updateNode` with appropriate information.
167+
168+
If you want to use *Metal3-FKAS* with another CAPI provider, you can also implement
169+
your own reconciler, based on implementation of *metal3-fkas-reconciler*.
170+
171+
### Deployment
172+
173+
The `metal3-fkas-system` deployment (including `metal3-fkas` and `metal3-fkas-reconciler`)
174+
can be deployed with the `k8s/metal3-fkas-system.yaml` file.
175+
176+
```shell
177+
kubectl apply -f k8s/metal3-fkas-system.yaml
178+
```
179+
180+
## Disclaimer
181+
182+
This is intended for development environments only.
183+
Do **NOT** use it in production.

0 commit comments

Comments
 (0)