diff --git a/README.md b/README.md index 03c0cb9..78ac719 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,24 @@ To provision the grafana agent only to an existing k8s system, ensure that the c provs-server.jar k3s myuser@myhost.com -o grafana ``` +To add the hetzner csi driver and encrypted volumes to your k3s installation add the following to the config: + +```yaml +hetzner: + hcloudApiToken: + source: "PLAIN" # PLAIN, GOPASS or PROMPT + parameter: "mypassword" # the api key for the hetzner cloud + encryptionPassphrase: + source: "PLAIN" # PLAIN, GOPASS or PROMPT + parameter: "mypassword" # the encryption passphrase for created volumes +``` + +To provision the grafana agent only to an existing k8s system, ensure that the config (as above) is available and execute: + +```bash +provs-server.jar k3s myuser@myhost.com -o grafana +``` + Reprovisioning the server can easily be done using the -r or --reprovision option. ```bash diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/server/domain/hetzner_csi/HetznerCSI.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/server/domain/hetzner_csi/HetznerCSI.kt new file mode 100644 index 0000000..2d5df8b --- /dev/null +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/server/domain/hetzner_csi/HetznerCSI.kt @@ -0,0 +1,7 @@ +package org.domaindrivenarchitecture.provs.server.domain.hetzner_csi + +import org.domaindrivenarchitecture.provs.framework.core.Prov +import org.domaindrivenarchitecture.provs.server.infrastructure.provisionHetznerCSIForK8s + +fun Prov.provisionHetznerCSI(configResolved: HetznerCSIConfigResolved) = + provisionHetznerCSIForK8s(configResolved.hcloudApiToken, configResolved.encryptionPassphrase) \ No newline at end of file diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/server/domain/hetzner_csi/HetznerCSIConfig.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/server/domain/hetzner_csi/HetznerCSIConfig.kt new file mode 100644 index 0000000..acb991c --- /dev/null +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/server/domain/hetzner_csi/HetznerCSIConfig.kt @@ -0,0 +1,23 @@ +package org.domaindrivenarchitecture.provs.server.domain.hetzner_csi + +import kotlinx.serialization.Serializable +import org.domaindrivenarchitecture.provs.framework.core.Secret +import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSupplier + +@Serializable +data class HetznerCSIConfig ( + val hcloudApiToken: SecretSupplier, + val encryptionPassphrase: SecretSupplier, +) { + fun resolveSecret(): HetznerCSIConfigResolved = HetznerCSIConfigResolved(this) +} + +data class HetznerCSIConfigResolved(val configUnresolved: HetznerCSIConfig) { + val hcloudApiToken: Secret = configUnresolved.hcloudApiToken.secret() + val encryptionPassphrase: Secret = configUnresolved.encryptionPassphrase.secret() +} + +@Serializable +data class HetznerCSIConfigHolder( + val hetzner: HetznerCSIConfig +) \ No newline at end of file diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/server/domain/k3s/K3sService.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/server/domain/k3s/K3sService.kt index 6f8af87..bcee2a7 100644 --- a/src/main/kotlin/org/domaindrivenarchitecture/provs/server/domain/k3s/K3sService.kt +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/server/domain/k3s/K3sService.kt @@ -2,6 +2,8 @@ package org.domaindrivenarchitecture.provs.server.domain.k3s import org.domaindrivenarchitecture.provs.configuration.infrastructure.DefaultConfigFileRepository import org.domaindrivenarchitecture.provs.framework.core.Prov +import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.HetznerCSIConfigResolved +import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.provisionHetznerCSI import org.domaindrivenarchitecture.provs.server.domain.k8s_grafana_agent.GrafanaAgentConfigResolved import org.domaindrivenarchitecture.provs.server.domain.k8s_grafana_agent.provisionGrafanaAgent import org.domaindrivenarchitecture.provs.server.infrastructure.* @@ -11,6 +13,7 @@ import kotlin.system.exitProcess fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task { val grafanaConfigResolved: GrafanaAgentConfigResolved? = findK8sGrafanaConfig(cli.configFileName)?.resolveSecret() + val hcloudConfigResolved: HetznerCSIConfigResolved? = findHetznerCSIConfig(cli.configFileName)?.resolveSecret() if (cli.onlyModules == null) { val k3sConfig: K3sConfig = getK3sConfig(cli.configFileName) @@ -18,9 +21,10 @@ fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task { val k3sConfigReprovision = k3sConfig.copy(reprovision = cli.reprovision || k3sConfig.reprovision) val applicationFile = cli.applicationFileName?.let { DefaultApplicationFileRepository(cli.applicationFileName).getFile() } - provisionK3s(k3sConfigReprovision, grafanaConfigResolved, applicationFile) + provisionK3s(k3sConfigReprovision, grafanaConfigResolved, hcloudConfigResolved, applicationFile) } else { provisionGrafana(cli.onlyModules, grafanaConfigResolved) + provisionHetznerCSI(cli.onlyModules, hcloudConfigResolved) } } @@ -30,6 +34,7 @@ fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task { fun Prov.provisionK3s( k3sConfig: K3sConfig, grafanaConfigResolved: GrafanaAgentConfigResolved? = null, + hetznerCSIConfigResolved: HetznerCSIConfigResolved? = null, applicationFile: ApplicationFile? = null ) = task { @@ -53,6 +58,10 @@ fun Prov.provisionK3s( provisionGrafanaAgent(grafanaConfigResolved) } + if (hetznerCSIConfigResolved != null) { + provisionHetznerCSI(hetznerCSIConfigResolved) + } + if (applicationFile != null) { provisionK3sApplication(applicationFile) } @@ -75,3 +84,18 @@ private fun Prov.provisionGrafana( provisionGrafanaAgent(grafanaConfigResolved) } } + +private fun Prov.provisionHetznerCSI( + onlyModules: List?, + hetznerCSIConfigResolved: HetznerCSIConfigResolved? +) = task { + + if (onlyModules != null && onlyModules.contains(ServerOnlyModule.HETZNER_CSI.name.lowercase())) { + if (hetznerCSIConfigResolved == null) { + println("ERROR: Could not find grafana config.") + exitProcess(7) + } + provisionHetznerCSI(hetznerCSIConfigResolved) + } + +} diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/server/domain/k3s/ServerSubModule.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/server/domain/k3s/ServerSubModule.kt index eccc637..07613f2 100644 --- a/src/main/kotlin/org/domaindrivenarchitecture/provs/server/domain/k3s/ServerSubModule.kt +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/server/domain/k3s/ServerSubModule.kt @@ -1,5 +1,6 @@ package org.domaindrivenarchitecture.provs.server.domain.k3s enum class ServerOnlyModule { - GRAFANA + GRAFANA, + HETZNER_CSI } diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/server/infrastructure/HetznerCSI.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/server/infrastructure/HetznerCSI.kt new file mode 100644 index 0000000..32f59f2 --- /dev/null +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/server/infrastructure/HetznerCSI.kt @@ -0,0 +1,53 @@ +package org.domaindrivenarchitecture.provs.server.infrastructure + +import org.domaindrivenarchitecture.provs.framework.core.Prov +import org.domaindrivenarchitecture.provs.framework.core.Secret +import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFileFromResource +import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFileFromResourceTemplate +import org.domaindrivenarchitecture.provs.server.domain.k3s.FileMode +import java.io.File + +private const val hetznerCSIResourceDir = "org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/" +fun Prov.provisionHetznerCSIForK8s(hetznerApiToken: Secret, encryptionPassphrase: Secret) { + // CSI Driver + createFileFromResourceTemplate( + k3sManualManifestsDir + "hcloud-api-token-secret.yaml", + "hcloud-api-token-secret.template.yaml", + resourcePath = hetznerCSIResourceDir, + posixFilePermission = "644", + values = mapOf( + "HETZNER_API_TOKEN" to hetznerApiToken.plain() + )) + applyHetznerCSIFileFromResource(File(k3sManualManifestsDir, "hcloud-api-token-secret.yaml")) + applyHetznerCSIFileFromResource(File(k3sManualManifestsDir, "hcloud-csi.yaml")) + + // Encryption + createFileFromResourceTemplate( + k3sManualManifestsDir + "hcloud-encryption-secret.yaml", + "hcloud-encryption-secret.template.yaml", + resourcePath = hetznerCSIResourceDir, + posixFilePermission = "644", + values = mapOf( + "HETZNER_ENCRYPTION_PASSPHRASE" to encryptionPassphrase.plain() + )) + applyHetznerCSIFileFromResource(File(k3sManualManifestsDir, "hcloud-encryption-secret.yaml")) + applyHetznerCSIFileFromResource(File(k3sManualManifestsDir, "hcloud-encrypted-storage-class.yaml")) +} + +private fun Prov.createHetznerCSIFileFromResource( + file: File, + posixFilePermission: FileMode? = "644" +) = task { + createFileFromResource( + file.path, + file.name, + hetznerCSIResourceDir, + posixFilePermission, + sudo = true + ) +} + +private fun Prov.applyHetznerCSIFileFromResource(file: File, posixFilePermission: FileMode? = "644") = task { + createHetznerCSIFileFromResource(file, posixFilePermission) + cmd("kubectl apply -f ${file.path}", sudo = true) +} \ No newline at end of file diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/server/infrastructure/HetznerCSIRepository.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/server/infrastructure/HetznerCSIRepository.kt new file mode 100644 index 0000000..7049f4a --- /dev/null +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/server/infrastructure/HetznerCSIRepository.kt @@ -0,0 +1,31 @@ +package org.domaindrivenarchitecture.provs.server.infrastructure + +import com.charleskorn.kaml.MissingRequiredPropertyException +import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName +import org.domaindrivenarchitecture.provs.framework.core.readFromFile +import org.domaindrivenarchitecture.provs.framework.core.toYaml +import org.domaindrivenarchitecture.provs.framework.core.yamlToType +import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.HetznerCSIConfig +import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.HetznerCSIConfigHolder +import java.io.File +import java.io.FileWriter + +private const val DEFAULT_CONFIG_FILE = "server-config.yaml" + +fun findHetznerCSIConfig(fileName: ConfigFileName? = null): HetznerCSIConfig? { + val filePath = fileName?.fileName ?: DEFAULT_CONFIG_FILE + + return if(File(filePath).exists()) { + try { + readFromFile(filePath).yamlToType().hetzner + } catch (e: MissingRequiredPropertyException) { + if (e.message.contains("Property 'hetzner'")) null else throw e + } + } else { + null + } +} + +@Suppress("unused") +internal fun writeConfig(config: HetznerCSIConfigHolder, fileName: String = "hetzner-config.yaml") = + FileWriter(fileName).use { it.write(config.toYaml()) } \ No newline at end of file diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/server/infrastructure/K3s.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/server/infrastructure/K3s.kt index 4ac8d13..cb77751 100644 --- a/src/main/kotlin/org/domaindrivenarchitecture/provs/server/infrastructure/K3s.kt +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/server/infrastructure/K3s.kt @@ -37,8 +37,9 @@ private val k3sEchoWithTls = File(k3sManualManifestsDir, "echo-tls.yaml") private val k3sEchoNoTls = File(k3sManualManifestsDir, "echo-no-tls.yaml") private val selfSignedCertificate = File(k3sManualManifestsDir, "selfsigned-certificate.yaml") -private val localPathProvisionerConfig = File(k3sManualManifestsDir, "local-path-provisioner-config.yaml") +private val hetznerCSIDriver = File(k3sManualManifestsDir, "hcloud-csi.yaml") +private val localPathProvisionerConfig = File(k3sManualManifestsDir, "local-path-provisioner-config.yaml") // ----------------------------------- public functions -------------------------------- @@ -124,6 +125,10 @@ fun Prov.installK3s(k3sConfig: K3sConfig): ProvResult { applyK3sFileFromResource(k3sMiddleWareHttpsRedirect) } + // hetzner csi-driver + applyK3sFileFromResource(hetznerCSIDriver) + + // other applyK3sFileFromResource(localPathProvisionerConfig) cmd("kubectl set env deployment -n kube-system local-path-provisioner DEPLOY_DATE=\"$(date)\"", sudo = true) diff --git a/src/main/resources/org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/hcloud-api-token-secret.template.yaml b/src/main/resources/org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/hcloud-api-token-secret.template.yaml new file mode 100644 index 0000000..997a7dd --- /dev/null +++ b/src/main/resources/org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/hcloud-api-token-secret.template.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: hcloud + namespace: kube-system +stringData: + token: $HETZNER_API_TOKEN \ No newline at end of file diff --git a/src/main/resources/org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/hcloud-csi.yaml b/src/main/resources/org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/hcloud-csi.yaml new file mode 100644 index 0000000..d988dca --- /dev/null +++ b/src/main/resources/org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/hcloud-csi.yaml @@ -0,0 +1,401 @@ +# Version 2.6.0 +# Source: hcloud-csi/templates/controller/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: hcloud-csi-controller + namespace: "kube-system" + labels: + app.kubernetes.io/name: hcloud-csi + app.kubernetes.io/instance: hcloud-csi + app.kubernetes.io/component: controller +automountServiceAccountToken: true +--- +# Source: hcloud-csi/templates/core/storageclass.yaml +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: hcloud-volumes + annotations: + storageclass.kubernetes.io/is-default-class: "true" +provisioner: csi.hetzner.cloud +volumeBindingMode: WaitForFirstConsumer +allowVolumeExpansion: true +reclaimPolicy: "Delete" +--- +# Source: hcloud-csi/templates/controller/clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: hcloud-csi-controller + labels: + app.kubernetes.io/name: hcloud-csi + app.kubernetes.io/instance: hcloud-csi + app.kubernetes.io/component: controller +rules: + # attacher + - apiGroups: [""] + resources: [persistentvolumes] + verbs: [get, list, watch, update, patch] + - apiGroups: [""] + resources: [nodes] + verbs: [get, list, watch] + - apiGroups: [csi.storage.k8s.io] + resources: [csinodeinfos] + verbs: [get, list, watch] + - apiGroups: [storage.k8s.io] + resources: [csinodes] + verbs: [get, list, watch] + - apiGroups: [storage.k8s.io] + resources: [volumeattachments] + verbs: [get, list, watch, update, patch] + - apiGroups: [storage.k8s.io] + resources: [volumeattachments/status] + verbs: [patch] + # provisioner + - apiGroups: [""] + resources: [secrets] + verbs: [get, list] + - apiGroups: [""] + resources: [persistentvolumes] + verbs: [get, list, watch, create, delete, patch] + - apiGroups: [""] + resources: [persistentvolumeclaims, persistentvolumeclaims/status] + verbs: [get, list, watch, update, patch] + - apiGroups: [storage.k8s.io] + resources: [storageclasses] + verbs: [get, list, watch] + - apiGroups: [""] + resources: [events] + verbs: [list, watch, create, update, patch] + - apiGroups: [snapshot.storage.k8s.io] + resources: [volumesnapshots] + verbs: [get, list] + - apiGroups: [snapshot.storage.k8s.io] + resources: [volumesnapshotcontents] + verbs: [get, list] + # resizer + - apiGroups: [""] + resources: [pods] + verbs: [get, list, watch] + # node + - apiGroups: [""] + resources: [events] + verbs: [get, list, watch, create, update, patch] +--- +# Source: hcloud-csi/templates/controller/clusterrolebinding.yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: hcloud-csi-controller + labels: + app.kubernetes.io/name: hcloud-csi + app.kubernetes.io/instance: hcloud-csi + app.kubernetes.io/component: controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: hcloud-csi-controller +subjects: + - kind: ServiceAccount + name: hcloud-csi-controller + namespace: "kube-system" +--- +# Source: hcloud-csi/templates/controller/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: hcloud-csi-controller-metrics + namespace: "kube-system" + labels: + app.kubernetes.io/name: hcloud-csi + app.kubernetes.io/instance: hcloud-csi + app.kubernetes.io/component: controller +spec: + ports: + - name: metrics + port: 9189 + selector: + app.kubernetes.io/name: hcloud-csi + app.kubernetes.io/instance: hcloud-csi + app.kubernetes.io/component: controller +--- +# Source: hcloud-csi/templates/node/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: hcloud-csi-node-metrics + namespace: "kube-system" + labels: + app.kubernetes.io/name: hcloud-csi + app.kubernetes.io/instance: hcloud-csi + app.kubernetes.io/component: node +spec: + ports: + - name: metrics + port: 9189 + selector: + app.kubernetes.io/name: hcloud-csi + app.kubernetes.io/instance: hcloud-csi + app.kubernetes.io/component: node +--- +# Source: hcloud-csi/templates/node/daemonset.yaml +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: hcloud-csi-node + namespace: "kube-system" + labels: + app.kubernetes.io/name: hcloud-csi + app.kubernetes.io/instance: hcloud-csi + app.kubernetes.io/component: node + app: hcloud-csi +spec: + updateStrategy: + type: RollingUpdate + selector: + matchLabels: + app: hcloud-csi + template: + metadata: + labels: + app.kubernetes.io/name: hcloud-csi + app.kubernetes.io/instance: hcloud-csi + app.kubernetes.io/component: node + app: hcloud-csi + spec: + + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: instance.hetzner.cloud/is-root-server + operator: NotIn + values: + - "true" + tolerations: + - effect: NoExecute + operator: Exists + - effect: NoSchedule + operator: Exists + - key: CriticalAddonsOnly + operator: Exists + securityContext: + fsGroup: 1001 + initContainers: + containers: + - name: csi-node-driver-registrar + image: registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.7.0 + imagePullPolicy: IfNotPresent + args: + - --kubelet-registration-path=/var/lib/kubelet/plugins/csi.hetzner.cloud/socket + volumeMounts: + - name: plugin-dir + mountPath: /run/csi + - name: registration-dir + mountPath: /registration + resources: + limits: {} + requests: {} + - name: liveness-probe + image: registry.k8s.io/sig-storage/livenessprobe:v2.9.0 + imagePullPolicy: IfNotPresent + volumeMounts: + - mountPath: /run/csi + name: plugin-dir + resources: + limits: {} + requests: {} + - name: hcloud-csi-driver + image: docker.io/hetznercloud/hcloud-csi-driver:v2.6.0 # x-release-please-version + imagePullPolicy: IfNotPresent + command: [/bin/hcloud-csi-driver-node] + volumeMounts: + - name: kubelet-dir + mountPath: /var/lib/kubelet + mountPropagation: "Bidirectional" + - name: plugin-dir + mountPath: /run/csi + - name: device-dir + mountPath: /dev + securityContext: + privileged: true + env: + - name: CSI_ENDPOINT + value: unix:///run/csi/socket + - name: METRICS_ENDPOINT + value: "0.0.0.0:9189" + - name: ENABLE_METRICS + value: "true" + ports: + - containerPort: 9189 + name: metrics + - name: healthz + protocol: TCP + containerPort: 9808 + resources: + limits: {} + requests: {} + livenessProbe: + failureThreshold: 5 + initialDelaySeconds: 10 + periodSeconds: 2 + successThreshold: 1 + timeoutSeconds: 3 + httpGet: + path: /healthz + port: healthz + volumes: + - name: kubelet-dir + hostPath: + path: /var/lib/kubelet + type: Directory + - name: plugin-dir + hostPath: + path: /var/lib/kubelet/plugins/csi.hetzner.cloud/ + type: DirectoryOrCreate + - name: registration-dir + hostPath: + path: /var/lib/kubelet/plugins_registry/ + type: Directory + - name: device-dir + hostPath: + path: /dev + type: Directory +--- +# Source: hcloud-csi/templates/controller/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: hcloud-csi-controller + namespace: "kube-system" + labels: + app.kubernetes.io/name: hcloud-csi + app.kubernetes.io/instance: hcloud-csi + app.kubernetes.io/component: controller + app: hcloud-csi-controller +spec: + replicas: 1 + strategy: + type: RollingUpdate + selector: + matchLabels: + app: hcloud-csi-controller + template: + metadata: + labels: + app.kubernetes.io/name: hcloud-csi + app.kubernetes.io/instance: hcloud-csi + app.kubernetes.io/component: controller + app: hcloud-csi-controller + spec: + serviceAccountName: hcloud-csi-controller + + securityContext: + fsGroup: 1001 + initContainers: + containers: + - name: csi-attacher + image: registry.k8s.io/sig-storage/csi-attacher:v4.1.0 + imagePullPolicy: IfNotPresent + resources: + limits: {} + requests: {} + args: + - --default-fstype=ext4 + volumeMounts: + - name: socket-dir + mountPath: /run/csi + + - name: csi-resizer + image: registry.k8s.io/sig-storage/csi-resizer:v1.7.0 + imagePullPolicy: IfNotPresent + resources: + limits: {} + requests: {} + volumeMounts: + - name: socket-dir + mountPath: /run/csi + + - name: csi-provisioner + image: registry.k8s.io/sig-storage/csi-provisioner:v3.4.0 + imagePullPolicy: IfNotPresent + resources: + limits: {} + requests: {} + args: + - --feature-gates=Topology=true + - --default-fstype=ext4 + volumeMounts: + - name: socket-dir + mountPath: /run/csi + + - name: liveness-probe + image: registry.k8s.io/sig-storage/livenessprobe:v2.9.0 + imagePullPolicy: IfNotPresent + resources: + limits: {} + requests: {} + volumeMounts: + - mountPath: /run/csi + name: socket-dir + + - name: hcloud-csi-driver + image: docker.io/hetznercloud/hcloud-csi-driver:v2.6.0 # x-release-please-version + imagePullPolicy: IfNotPresent + command: [/bin/hcloud-csi-driver-controller] + env: + - name: CSI_ENDPOINT + value: unix:///run/csi/socket + - name: METRICS_ENDPOINT + value: "0.0.0.0:9189" + - name: ENABLE_METRICS + value: "true" + - name: KUBE_NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName + - name: HCLOUD_TOKEN + valueFrom: + secretKeyRef: + name: hcloud + key: token + resources: + limits: {} + requests: {} + ports: + - name: metrics + containerPort: 9189 + - name: healthz + protocol: TCP + containerPort: 9808 + livenessProbe: + failureThreshold: 5 + initialDelaySeconds: 10 + periodSeconds: 2 + successThreshold: 1 + timeoutSeconds: 3 + httpGet: + path: /healthz + port: healthz + volumeMounts: + - name: socket-dir + mountPath: /run/csi + + volumes: + - name: socket-dir + emptyDir: {} +--- +# Source: hcloud-csi/templates/core/csidriver.yaml +apiVersion: storage.k8s.io/v1 +kind: CSIDriver +metadata: + name: csi.hetzner.cloud +spec: + attachRequired: true + fsGroupPolicy: File + podInfoOnMount: true + volumeLifecycleModes: + - Persistent diff --git a/src/main/resources/org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/hcloud-encrypted-storage-class.yaml b/src/main/resources/org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/hcloud-encrypted-storage-class.yaml new file mode 100644 index 0000000..3eb0b85 --- /dev/null +++ b/src/main/resources/org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/hcloud-encrypted-storage-class.yaml @@ -0,0 +1,11 @@ +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: hcloud-volumes-encrypted +provisioner: csi.hetzner.cloud +reclaimPolicy: Delete +volumeBindingMode: WaitForFirstConsumer +allowVolumeExpansion: true +parameters: + csi.storage.k8s.io/node-publish-secret-name: encryption-secret + csi.storage.k8s.io/node-publish-secret-namespace: kube-system \ No newline at end of file diff --git a/src/main/resources/org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/hcloud-encryption-secret.template.yaml b/src/main/resources/org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/hcloud-encryption-secret.template.yaml new file mode 100644 index 0000000..ef94802 --- /dev/null +++ b/src/main/resources/org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/hcloud-encryption-secret.template.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: encryption-secret + namespace: kube-system +stringData: + encryption-passphrase: $HETZNER_ENCRYPTION_PASSPHRASE \ No newline at end of file diff --git a/src/test/kotlin/org/domaindrivenarchitecture/provs/server/infrastructure/HetznerCSIRepositoryKtTest.kt b/src/test/kotlin/org/domaindrivenarchitecture/provs/server/infrastructure/HetznerCSIRepositoryKtTest.kt new file mode 100644 index 0000000..43cf44e --- /dev/null +++ b/src/test/kotlin/org/domaindrivenarchitecture/provs/server/infrastructure/HetznerCSIRepositoryKtTest.kt @@ -0,0 +1,35 @@ +package org.domaindrivenarchitecture.provs.server.infrastructure + +import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName +import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSourceType +import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSupplier +import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.HetznerCSIConfig +import org.domaindrivenarchitecture.provs.server.domain.k8s_grafana_agent.GrafanaAgentConfig +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +internal class HetznerCSIRepositoryKtTest { + + @Test + fun findHetznerCSIConfig_returns_config() { + // when + val config = findHetznerCSIConfig(ConfigFileName("src/test/resources/k3s-server-config-with-hetzner.yaml")) + + // then + assertEquals( + HetznerCSIConfig( + hcloudApiToken = SecretSupplier(SecretSourceType.GOPASS, "path/to/apitoken"), + encryptionPassphrase = SecretSupplier(SecretSourceType.GOPASS, "path/to/encryption"), + ), config + ) + } + + @Test + fun findHetznerCSIConfig_returns_null_if_no_hetzner_data_available() { + // when + val config = findHetznerCSIConfig(ConfigFileName("src/test/resources/k3s-server-config.yaml")) + + // then + assertEquals(null, config) + } +} \ No newline at end of file diff --git a/src/test/resources/k3s-server-config-with-hetzner.yaml b/src/test/resources/k3s-server-config-with-hetzner.yaml new file mode 100644 index 0000000..3fbe1e5 --- /dev/null +++ b/src/test/resources/k3s-server-config-with-hetzner.yaml @@ -0,0 +1,18 @@ +fqdn: statistics.test.meissa-gmbh.de +node: + ipv4: 162.55.164.138 + ipv6: 2a01:4f8:c010:672f::1 +certmanager: + email: admin@meissa-gmbh.de + letsencryptEndpoint: prod +echo: true +reprovision: true + + +hetzner: + hcloudApiToken: + source: "GOPASS" # PLAIN, GOPASS or PROMPT + parameter: "path/to/apitoken" # the api key for the hetzner cloud + encryptionPassphrase: + source: "GOPASS" # PLAIN, GOPASS or PROMPT + parameter: "path/to/encryption" # the encryption passphrase for created volumes \ No newline at end of file