Skip to content

Commit e74de04

Browse files
ostempelGerrit91
andauthored
Pod eviction controller (#117)
Co-authored-by: Gerrit <[email protected]>
1 parent d18b6a3 commit e74de04

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1469
-23
lines changed

.github/workflows/docker.yaml

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ env:
1515
REGISTRY: ghcr.io
1616
PLUGIN_IMAGE_NAME: ${{ github.repository }}
1717
PROVISIONER_IMAGE_NAME: ${{ github.repository }}-provisioner
18+
CONTROLLER_IMAGE_NAME: ${{ github.repository }}-controller
1819

1920
jobs:
2021
lint:
@@ -82,25 +83,28 @@ jobs:
8283
- name: Setup Go
8384
uses: actions/setup-go@v5
8485
with:
85-
go-version-file: 'go.mod'
86+
go-version-file: "go.mod"
8687

8788
- name: Build
8889
env:
8990
GOOS: ${{ matrix.os }}
9091
GOARCH: ${{ matrix.arch }}
9192
GOARM: ${{ matrix.arch == 'arm' && '7' || '' }}
9293
run: |
93-
make lvmplugin provisioner
94+
make lvmplugin provisioner controller
9495
9596
- uses: actions/upload-artifact@v4
9697
with:
9798
name: lvmplugin-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.arch == 'arm' && '-v7' || '' }}
9899
path: bin
99-
100100
- uses: actions/upload-artifact@v4
101101
with:
102102
name: provisioner-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.arch == 'arm' && '-v7' || '' }}
103103
path: bin
104+
- uses: actions/upload-artifact@v4
105+
with:
106+
name: controller-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.arch == 'arm' && '-v7' || '' }}
107+
path: bin
104108

105109
docker-build:
106110
runs-on: ubuntu-latest
@@ -131,20 +135,24 @@ jobs:
131135
- name: Set up Docker Buildx
132136
uses: docker/setup-buildx-action@v3
133137

134-
135138
- name: Download plugin binaries
136139
uses: actions/download-artifact@v4
137140
with:
138141
pattern: lvmplugin-*
139142
path: bin
140143
merge-multiple: true
141-
142144
- name: Download provisioner binaries
143145
uses: actions/download-artifact@v4
144146
with:
145147
pattern: provisioner-*
146148
path: bin
147149
merge-multiple: true
150+
- name: Download controller binaries
151+
uses: actions/download-artifact@v4
152+
with:
153+
pattern: controller-*
154+
path: bin
155+
merge-multiple: true
148156

149157
- name: Build and push plugin image
150158
if: ${{ env.DOCKER_REGISTRY_TOKEN != '' }}
@@ -167,3 +175,14 @@ jobs:
167175
tags: ${{ env.REGISTRY }}/${{ env.PROVISIONER_IMAGE_NAME }}:${{ env.tag }}
168176
file: cmd/provisioner/Dockerfile
169177
platforms: linux/amd64,linux/arm64,linux/arm/v7
178+
179+
- name: Build and push controller image
180+
if: ${{ env.DOCKER_REGISTRY_TOKEN != '' }}
181+
uses: docker/build-push-action@v6
182+
with:
183+
context: .
184+
push: true
185+
sbom: true
186+
tags: ${{ env.REGISTRY }}/${{ env.CONTROLLER_IMAGE_NAME }}:${{ env.tag }}
187+
file: cmd/controller/Dockerfile
188+
platforms: linux/amd64,linux/arm64,linux/arm/v7

.gitignore

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,31 @@ loop*
55
# editor specific files
66
.idea
77
.vscode
8+
9+
# Binaries for programs and plugins
10+
*.exe
11+
*.exe~
12+
*.dll
13+
*.so
14+
*.dylib
15+
bin/*
16+
Dockerfile.cross
17+
18+
# Test binary, built with `go test -c`
19+
*.test
20+
21+
# Output of the go coverage tool, specifically when used with LiteIDE
22+
*.out
23+
24+
# Go workspace file
25+
go.work
26+
27+
# Kubernetes Generated files - skip generated files, except for vendored files
28+
!vendor/**/zz_generated.*
29+
30+
# editor and IDE paraphernalia
31+
.idea
32+
.vscode
33+
*.swp
34+
*.swo
35+
*~

Makefile

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
11
GOOS ?= linux
22
GOARCH ?= amd64
3-
GOARM ?=
3+
GOARM ?=
44
CGO_ENABLED ?= 0
55
TAGS := -tags 'osusergo netgo static_build'
66

77
PLATFORM := $(GOOS)/$(GOARCH)$(if $(GOARM),/v$(GOARM))
88
BINARY_LVMPLUGIN := $(PLATFORM)/lvmplugin
99
BINARY_PROVISIONER:= $(PLATFORM)/provisioner
10+
BINARY_CONTROLLER:= $(PLATFORM)/controller
11+
12+
GO111MODULE := on
13+
KUBECONFIG := $(shell pwd)/.kubeconfig
14+
HELM_REPO := "https://helm.metal-stack.io"
1015

1116
SHA := $(shell git rev-parse --short=8 HEAD)
1217
GITVERSION := $(shell git describe --long --all)
13-
BUILDDATE := $(shell date --rfc-3339=seconds)
18+
# gnu date format iso-8601 is parsable with Go RFC3339
19+
BUILDDATE := $(shell date --iso-8601=seconds)
1420
VERSION := $(or ${VERSION},$(shell git describe --tags --exact-match 2> /dev/null || git symbolic-ref -q --short HEAD || git rev-parse --short HEAD))
1521

16-
GO111MODULE := on
17-
KUBECONFIG := $(shell pwd)/.kubeconfig
18-
HELM_REPO := "https://helm.metal-stack.io"
22+
CONTROLLER_TOOLS_VERSION ?= v0.18.0
23+
LOCALBIN ?= $(shell pwd)/bin
24+
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
25+
ENVTEST ?= $(LOCALBIN)/setup-envtest
26+
27+
$(LOCALBIN):
28+
mkdir -p $(LOCALBIN)
1929

2030
ifeq ($(CI),true)
2131
DOCKER_TTY_ARG=
@@ -70,7 +80,7 @@ build-provisioner: provisioner
7080
docker build -t csi-driver-lvm-provisioner -f cmd/provisioner/Dockerfile .
7181

7282
/dev/loop%:
73-
@fallocate --length 2G loop$*.img
83+
@fallocate --length 4G loop$*.img
7484
ifndef GITHUB_ACTIONS
7585
@sudo mknod $@ b 7 $*
7686
endif
@@ -96,25 +106,87 @@ kind:
96106
--kubeconfig $(KUBECONFIG); fi
97107
@kind --name csi-driver-lvm load docker-image csi-driver-lvm
98108
@kind --name csi-driver-lvm load docker-image csi-driver-lvm-provisioner
109+
@kind --name csi-driver-lvm load docker-image csi-driver-lvm-controller
99110

100111
.PHONY: rm-kind
101112
rm-kind:
102113
@kind delete cluster --name csi-driver-lvm
103114

104115
RERUN ?= 1
105116
.PHONY: test
106-
test: build-plugin build-provisioner /dev/loop100 /dev/loop101 kind
117+
test: build-plugin build-provisioner build-controller /dev/loop100 /dev/loop101 kind
107118
@cd tests && docker build -t csi-bats . && cd -
108119
@touch $(KUBECONFIG)
109120
@for i in {1..$(RERUN)}; do \
110121
docker run -i$(DOCKER_TTY_ARG) \
111122
-e HELM_REPO=$(HELM_REPO) \
112123
-v "$(KUBECONFIG):/root/.kube/config" \
113124
-v "$(PWD)/tests:/code" \
125+
-v "$(PWD)/config:/config" \
114126
--network host \
115127
csi-bats \
116128
--verbose-run --trace --timing bats/test.bats ; \
117129
done
118130

119131
.PHONY: test-cleanup
120132
test-cleanup: rm-kind
133+
134+
#!
135+
#! CONTROLLER
136+
#!
137+
138+
.PHONY: controller
139+
controller: generate fmt #vet
140+
go mod tidy
141+
go build \
142+
$(TAGS) \
143+
-ldflags \
144+
"$(LINKMODE)" \
145+
-o bin/$(BINARY_CONTROLLER) \
146+
./cmd/controller/main.go
147+
cd bin/ && \
148+
sha512sum $(BINARY_CONTROLLER) > $(BINARY_CONTROLLER).sha512
149+
150+
.PHONY: build-controller
151+
build-controller: controller
152+
docker build -t csi-driver-lvm-controller -f cmd/controller/Dockerfile .
153+
154+
.PHONY: manifests
155+
manifests: controller-gen
156+
$(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
157+
158+
deploy: manifests
159+
cd config/manager && kustomize edit set image controller=csi-driver-lvm-controller
160+
kustomize build config/default | kubectl apply -f -
161+
162+
.PHONY: undeploy
163+
undeploy:
164+
kustomize build config/default | kubectl delete -f -
165+
166+
.PHONY: generate
167+
generate: controller-gen manifests
168+
go generate ./...
169+
$(CONTROLLER_GEN) object paths="./..."
170+
171+
.PHONY: fmt
172+
fmt:
173+
go fmt ./...
174+
175+
.PHONY: vet
176+
vet:
177+
go vet ./...
178+
179+
# .PHONY: test
180+
# test: manifests generate fmt vet setup-envtest ## Run tests.
181+
# KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out
182+
183+
.PHONY: controller-gen
184+
controller-gen: $(CONTROLLER_GEN)
185+
$(CONTROLLER_GEN): $(LOCALBIN)
186+
test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \
187+
GOOS= GOARCH= GOARM= GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION)
188+
189+
.PHONY: setup-envtest
190+
setup-envtest: $(ENVTEST)
191+
$(ENVTEST): $(LOCALBIN)
192+
test -s $(LOCALBIN)/setup-envtest || GOOS= GOARCH= GOARM= GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ This CSI driver is derived from [csi-driver-host-path](https://github.com/kubern
1212

1313
For the special case of block volumes, the filesystem-expansion has to be performed by the app using the block device
1414

15+
## Automatic PVC Deletion on Pod Eviction
16+
17+
The persistent volumes created by this CSI driver are strictly node-affine to the node on which the pod was scheduled. This is intentional and prevents pods from starting without the LV data, which resides only on the specific node in the Kubernetes cluster.
18+
19+
Consequently, if a pod is evicted (potentially due to cluster autoscaling or updates to the worker node), the pod may become stuck. In certain scenarios, it's acceptable for the pod to start on another node, despite the potential for data loss. The csi-driver-lvm-controller can capture these events and automatically delete the PVC without requiring manual intervention by an operator.
20+
21+
To use this functionality, the following is needed:
22+
23+
- This only works on `StatefulSet`s with volumeClaimTemplates and volume references to the `csi-driver-lvm` storage class
24+
- In addition to that, the `Pod` or `PersistentVolumeClaim` managed by the `StatefulSet` needs the annotation: `metal-stack.io/csi-driver-lvm.is-eviction-allowed: true`
25+
1526
## Installation ##
1627

1728
**Helm charts for installation are located in a separate repository called [helm-charts](https://github.com/metal-stack/helm-charts). If you would like to contribute to the helm chart, please raise an issue or pull request there.**
@@ -65,6 +76,7 @@ You can create these loop devices like this:
6576
```bash
6677
for i in 100 101; do fallocate -l 1G loop${i}.img ; sudo losetup /dev/loop${i} loop${i}.img; done
6778
sudo losetup -a
79+
# https://github.com/util-linux/util-linux/issues/3197
6880
# use this for recreation or cleanup
6981
# for i in 100 101; do sudo losetup -d /dev/loop${i}; rm -f loop${i}.img; done
7082
```

cmd/controller/Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM gcr.io/distroless/static:nonroot
2+
ARG TARGETPLATFORM
3+
LABEL maintainer="metal-stack authors <[email protected]>"
4+
5+
WORKDIR /
6+
COPY --chmod=755 bin/${TARGETPLATFORM}/controller /controller
7+
USER 65532:65532
8+
ENTRYPOINT ["/controller"]

cmd/controller/main.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"os"
6+
7+
"github.com/metal-stack/csi-driver-lvm/pkg/controller"
8+
_ "k8s.io/client-go/plugin/pkg/client/auth"
9+
10+
"k8s.io/apimachinery/pkg/runtime"
11+
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
12+
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
13+
ctrl "sigs.k8s.io/controller-runtime"
14+
"sigs.k8s.io/controller-runtime/pkg/healthz"
15+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
16+
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
17+
// +kubebuilder:scaffold:imports
18+
)
19+
20+
var (
21+
scheme = runtime.NewScheme()
22+
setupLog = ctrl.Log.WithName("setup")
23+
)
24+
25+
func init() {
26+
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
27+
28+
// +kubebuilder:scaffold:scheme
29+
}
30+
31+
// nolint:gocyclo
32+
func main() {
33+
var (
34+
provisionerName string
35+
logLevel string
36+
metricsAddr string
37+
enableLeaderElection bool
38+
probeAddr string
39+
)
40+
flag.StringVar(&provisionerName, "provisioner-name", "lvm.csi.metal-stack.io", "The provisioner name of the csi-driver.")
41+
flag.StringVar(&logLevel, "log-level", "info", "The log level of the application.")
42+
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
43+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
44+
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
45+
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
46+
"Enable leader election for controller manager. "+
47+
"Enabling this will ensure there is only one active controller manager.")
48+
49+
opts := zap.Options{
50+
Development: true,
51+
}
52+
opts.BindFlags(flag.CommandLine)
53+
flag.Parse()
54+
55+
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
56+
57+
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
58+
Scheme: scheme,
59+
Metrics: server.Options{
60+
BindAddress: metricsAddr,
61+
},
62+
HealthProbeBindAddress: probeAddr,
63+
LeaderElection: enableLeaderElection,
64+
LeaderElectionID: "814f7540.csi-driver-lvm-controller",
65+
})
66+
if err != nil {
67+
setupLog.Error(err, "unable to start manager")
68+
os.Exit(1)
69+
}
70+
71+
// +kubebuilder:scaffold:builder
72+
73+
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
74+
setupLog.Error(err, "unable to set up health check")
75+
os.Exit(1)
76+
}
77+
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
78+
setupLog.Error(err, "unable to set up ready check")
79+
os.Exit(1)
80+
}
81+
82+
reconciler := controller.New(mgr.GetClient(), mgr.GetScheme(), ctrl.Log.WithName("CsiDriverLvmReconciler"), controller.Config{ProvisionerName: provisionerName})
83+
84+
if err := reconciler.SetupWithManager(mgr); err != nil {
85+
setupLog.Error(err, "unable to create controller", "controller", "CsiDriverLvm")
86+
os.Exit(1)
87+
}
88+
89+
setupLog.Info("starting manager")
90+
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
91+
setupLog.Error(err, "problem running manager")
92+
os.Exit(1)
93+
}
94+
}

cmd/lvmplugin/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ ARG TARGETPLATFORM
33
LABEL maintainer="metal-stack authors <[email protected]>"
44

55
RUN apk add lvm2 lvm2-extra e2fsprogs e2fsprogs-extra smartmontools nvme-cli util-linux device-mapper xfsprogs xfsprogs-extra
6-
COPY bin/${TARGETPLATFORM}/lvmplugin /lvmplugin
6+
COPY --chmod=755 bin/${TARGETPLATFORM}/lvmplugin /lvmplugin
77
USER root
88
ENTRYPOINT ["/lvmplugin"]

cmd/provisioner/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ ARG TARGETPLATFORM
33
LABEL maintainer="metal-stack authors <[email protected]>"
44

55
RUN apk add lvm2 lvm2-extra e2fsprogs e2fsprogs-extra xfsprogs-extra smartmontools nvme-cli util-linux device-mapper
6-
COPY bin/${TARGETPLATFORM}/provisioner /csi-lvmplugin-provisioner
6+
COPY --chmod=755 bin/${TARGETPLATFORM}/provisioner /csi-lvmplugin-provisioner
77
USER root
88
ENTRYPOINT ["/csi-lvmplugin-provisioner"]

0 commit comments

Comments
 (0)