-
-
Notifications
You must be signed in to change notification settings - Fork 131
feat: add kubernetes deployment configuration using helm #263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jcstryker
wants to merge
20
commits into
tale:main
Choose a base branch
from
jcstryker:feat/add-helm-chart
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
f178db5
feat: initial helm chart migration into headplane upstream
jcstryker 9a7c7ea
feat: add support for hostAliases in deployment
jcstryker 58b65fc
Merge branch 'tale:main' into feat/add-helm-chart
jcstryker eb50611
fix: check for public headscale url
jcstryker bca3c77
feat: move headscale config to configmap
jcstryker 368cdf4
feat: add empty split dns map
jcstryker f56fbf8
chore: enable startup api check by default
jcstryker 314cac0
fix(kubernetes): remove unused pvc templates
jcstryker 4942ede
bump versions
jcstryker 73e7173
feat: support new secret passing paths
jcstryker a01e21a
feat(kubernetes): add persistent volume claims for headplane and head…
jcstryker d54fa07
fix(kubernetes): correct persistent volume claim name for headscale-data
jcstryker 6739dd6
feat: test github action + match semver
jcstryker 7372473
fix: match semver
jcstryker 9c08746
feat: allow test branch workflow
jcstryker 5d83429
fix: hardcode chart name
jcstryker ca4e435
test: chart releaser
jcstryker 0db4402
fix: chart releaser
jcstryker 27a1688
fix: weird bash error
jcstryker 3c5cea0
chore: move chart publish workflow to separate pr
jcstryker File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| apiVersion: v2 | ||
| appVersion: 0.6.1 | ||
| description: Kubernetes Helm Chart for Headplane in integrated mode | ||
| name: headplane | ||
| type: application | ||
| version: 1.0.0 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| {{- define "headplane.cookieSecret" -}} | ||
| {{- if .Values.headplane.config.cookieSecret.value -}} | ||
| {{- .Values.headplane.config.cookieSecret.value -}} | ||
| {{- else -}} | ||
| {{- randAlphaNum 32 -}} | ||
| {{- end -}} | ||
| {{- end -}} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| --- | ||
| apiVersion: apps/v1 | ||
| kind: Deployment | ||
| metadata: | ||
| name: headplane-integrated | ||
| spec: | ||
| replicas: 1 | ||
| selector: | ||
| matchLabels: | ||
| app: headplane-integrated | ||
| strategy: | ||
| type: Recreate | ||
| template: | ||
| metadata: | ||
| labels: | ||
| app: headplane-integrated | ||
| annotations: | ||
| checksum/configmap-headplane: {{ include (print $.Template.BasePath "/headplane/configmap.yaml") . | sha256sum }} | ||
| checksum/secret-headplane: {{ include (print $.Template.BasePath "/headplane/secrets.yaml") . | sha256sum }} | ||
| checksum/secret-headscale: {{ include (print $.Template.BasePath "/headscale/secret.yaml") . | sha256sum }} | ||
| spec: | ||
| hostAliases: {{- toYaml .Values.hostAliases | nindent 8 }} | ||
| shareProcessNamespace: true | ||
| serviceAccountName: headplane-integrated | ||
| securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} | ||
| containers: | ||
| - name: headplane | ||
| image: "{{ required "headplane image repository required" .Values.headplane.image.repository }}:{{ .Values.headplane.image.tag | default .Chart.AppVersion }}" | ||
| imagePullPolicy: {{ required "headplane image pull policy required" .Values.headplane.image.pullPolicy }} | ||
| env: | ||
| - name: HEADPLANE_DEBUG_LOG | ||
| value: {{ .Values.headplane.config.debug | quote }} | ||
| - name: HEADPLANE_LOAD_ENV_OVERRIDES | ||
| value: "true" | ||
| - name: HEADPLANE_INTEGRATION__KUBERNETES__POD_NAME | ||
| valueFrom: | ||
| fieldRef: | ||
| fieldPath: metadata.name | ||
| ports: | ||
| - name: app | ||
| containerPort: 3000 | ||
| protocol: TCP | ||
| securityContext: {{- toYaml .Values.headplane.securityContext | nindent 12 }} | ||
| volumeMounts: | ||
| - name: headplane-config | ||
| mountPath: /etc/headplane | ||
| - name: headplane-server-cookie-secret | ||
| mountPath: /var/secrets/headplane/server | ||
| {{- if .Values.headplane.config.oidc.enabled }} | ||
| - name: headplane-oidc-client-secret | ||
| mountPath: /var/secrets/headplane/oidc | ||
| - name: headplane-oidc-headscale-api-key | ||
| mountPath: /var/secrets/headplane/headscale | ||
| {{- end }} | ||
| - name: headplane-data | ||
| mountPath: /var/lib/headplane | ||
| readOnly: false | ||
| - name: headscale-config | ||
| mountPath: /etc/headscale | ||
| initContainers: | ||
| - name: headscale | ||
| image: "{{ required "headscale image repository required" .Values.headscale.image.repository }}:{{ required "headscale image tag required" .Values.headscale.image.tag }}" | ||
| imagePullPolicy: {{ required "headscale image pull policy required" .Values.headscale.image.pullPolicy }} | ||
| restartPolicy: Always | ||
| args: | ||
| - serve | ||
| ports: | ||
| - name: api | ||
| containerPort: 8080 | ||
| protocol: TCP | ||
| - name: metrics | ||
| containerPort: 9090 | ||
| protocol: TCP | ||
| - name: grpc | ||
| containerPort: 50443 | ||
| protocol: TCP | ||
| - name: stun | ||
| containerPort: 3478 | ||
| protocol: UDP | ||
| securityContext: {{- toYaml .Values.headscale.securityContext | nindent 12 }} | ||
| volumeMounts: | ||
| - name: headscale-config | ||
| mountPath: /etc/headscale | ||
| {{- if .Values.headscale.config.oidc.enabled }} | ||
| - name: headscale-oidc-client-secret | ||
| mountPath: /var/secrets/headscale/oidc | ||
| {{- end }} | ||
| - name: headscale-data | ||
| mountPath: /var/lib/headscale | ||
| readOnly: false | ||
| {{- if .Values.headplane.config.generateCredentials }} | ||
| - name: generate-headscale-token | ||
| image: alpine/k8s:1.33.1 | ||
| env: | ||
| - name: NAMESPACE | ||
| valueFrom: | ||
| fieldRef: | ||
| fieldPath: metadata.namespace | ||
| - name: HEADSCALE_SECRET_NAME | ||
| value: {{ default "headscale-api-key" .Values.headplane.config.oidc.headscaleApiKey.secretName }} | ||
| - name: HEADSCALE_POD_SELECTOR | ||
| value: "app=headplane-integrated" | ||
| command: [ "bash", "-c" ] | ||
| args: [ "/etc/scripts/ensure-headscale-api-key.sh" ] | ||
| securityContext: | ||
| capabilities: | ||
| drop: | ||
| - ALL | ||
| readOnlyRootFilesystem: true | ||
| runAsNonRoot: true | ||
| runAsUser: 1000 | ||
| volumeMounts: | ||
| - name: headplane-scripts | ||
| mountPath: /etc/scripts | ||
| {{- end }} | ||
| volumes: | ||
| ### | ||
| ### Headplane Volumes ### | ||
| ### | ||
| - name: headplane-config | ||
| configMap: | ||
| name: headplane | ||
| - name: headplane-server-cookie-secret | ||
| secret: | ||
| secretName: {{ default "headplane-cookie-secret" .Values.headplane.config.cookieSecret.secretName }} | ||
| {{- if .Values.headplane.config.oidc.enabled }} | ||
| - name: headplane-oidc-client-secret | ||
| secret: | ||
| secretName: {{ default "headplane-oidc-client-secret" .Values.headplane.config.oidc.clientSecret.secretName }} | ||
| - name: headplane-oidc-headscale-api-key | ||
| secret: | ||
| secretName: {{ default "headscale-api-key" .Values.headplane.config.oidc.headscaleApiKey.secretName }} | ||
| {{- end }} | ||
| - name: headplane-data | ||
| {{- if .Values.headplane.persistence.enabled }} | ||
| persistentVolumeClaim: | ||
| claimName: headplane-data | ||
| {{- else }} | ||
| emptyDir: | ||
| sizeLimit: 500Mi | ||
| {{- end }} | ||
| {{- if .Values.headplane.config.generateCredentials }} | ||
| - name: headplane-scripts | ||
| configMap: | ||
| name: headplane-scripts | ||
| defaultMode: 0777 | ||
| {{- end }} | ||
| ### | ||
| ### Headscale Volumes ### | ||
| ### | ||
| - name: headscale-config | ||
| configMap: | ||
| name: headscale | ||
| {{- if .Values.headscale.config.oidc.enabled }} | ||
| - name: headscale-oidc-client-secret | ||
| secret: | ||
| secretName: {{ default "headscale-oidc-client-secret" .Values.headscale.config.oidc.clientSecret.secretName }} | ||
| {{- end }} | ||
| - name: headscale-data | ||
| {{- if .Values.headscale.persistence.enabled }} | ||
| persistentVolumeClaim: | ||
| claimName: headscale-data | ||
| {{- else }} | ||
| emptyDir: | ||
| sizeLimit: 500Mi | ||
| {{- end }} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| {{ range .Values.extraObjects }} | ||
| --- | ||
| {{ if typeIs "string" . }} | ||
| {{- tpl . $ }} | ||
| {{- else }} | ||
| {{- tpl (toYaml .) $ }} | ||
| {{- end }} | ||
| {{ end }} |
132 changes: 132 additions & 0 deletions
132
kubernetes/headplane/templates/headplane/configmap-scripts.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| {{- if .Values.headplane.config.generateCredentials }} | ||
| apiVersion: v1 | ||
| kind: ConfigMap | ||
| metadata: | ||
| name: headplane-scripts | ||
| data: | ||
| ensure-headscale-api-key.sh: | | ||
| set -e | ||
|
|
||
| NAMESPACE="${NAMESPACE:?Error: NAMESPACE environment variable not set.}" | ||
| HEADSCALE_SECRET_NAME="${HEADSCALE_SECRET_NAME:?Error: HEADSCALE_SECRET_NAME environment variable not set.}" | ||
| HEADSCALE_POD_SELECTOR="${HEADSCALE_POD_SELECTOR:?Error: HEADSCALE_POD_SELECTOR environment variable not set.}" | ||
|
|
||
| check_api_key_validity() { | ||
| local key_to_check="$1" | ||
| local validation_url="$HEADSCALE_HOST:8080/api/v1/user" | ||
|
|
||
| local http_status | ||
| local curl_stderr_output | ||
| local curl_exit_status | ||
|
|
||
| curl_stderr_output=$(curl -sS --fail -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $key_to_check" "$validation_url" 2>&1) | ||
| curl_exit_status=$? | ||
|
|
||
| http_status=$(echo "$curl_stderr_output" | tail -n 1) | ||
| curl_stderr_output=$(echo "$curl_stderr_output" | head -n -1) | ||
|
|
||
| if [[ "$curl_exit_status" -eq 0 && "$http_status" =~ ^2 ]]; then | ||
| echo "Headscale API Key is valid (HTTP $http_status OK)." | ||
| return 0 | ||
| else | ||
| echo "API Key validation failed." | ||
| echo "Debug Info:" | ||
| echo " Validation URL: $validation_url" | ||
| echo " Curl Exit Status: $curl_exit_status" | ||
| echo " HTTP Status Code: $http_status" | ||
| if [[ -n "$curl_stderr_output" ]]; then | ||
| echo " Curl Error Output: $curl_stderr_output" | ||
| fi | ||
| return 1 | ||
| fi | ||
| } | ||
|
|
||
| echo "Finding headscale pod in namespace '$NAMESPACE' with selector '$HEADSCALE_POD_SELECTOR'..." | ||
| HEADPLANE_POD=$(kubectl get pod -n "$NAMESPACE" -l "$HEADSCALE_POD_SELECTOR" -o jsonpath="{.items[0].metadata.name}" --ignore-not-found) | ||
|
|
||
| if [[ -z "$HEADPLANE_POD" ]]; then | ||
| echo "Error: No headscale pod found matching selector '$HEADSCALE_POD_SELECTOR' in namespace '$NAMESPACE'." | ||
| exit 1 | ||
| fi | ||
| echo "Success: Found headscale pod '$HEADPLANE_POD'" | ||
|
|
||
|
|
||
| echo "Checking 'headscale' container status in pod '$HEADPLANE_POD' for readiness..." | ||
|
|
||
| container_ready=$(kubectl get pod -n "$NAMESPACE" "$HEADPLANE_POD" -o jsonpath="{.status.initContainerStatuses[?(@.name==\"headscale\")].ready}" 2>/dev/null || echo "") | ||
|
|
||
| if [[ "$container_ready" == "true" ]]; then | ||
| echo "Success: 'headscale' container is ready" | ||
| else | ||
| echo "--- Headscale Container Readiness Check Failed ---" | ||
| echo "Error: 'headscale' container in pod '$HEADPLANE_POD' is NOT ready." | ||
| if [[ -z "$container_ready" ]]; then | ||
| echo " Reason: Container status not yet available (Pod might be starting or in a pending state)." | ||
| else | ||
| echo " Reason: Container status found, but reported as '$container_ready'." | ||
| fi | ||
| echo " Namespace: '$NAMESPACE'" | ||
| echo " Pod Name: '$HEADPLANE_POD'" | ||
| echo "---------------------------------------------------------" | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [[ -z "${HEADSCALE_HOST:-}" ]]; then | ||
| echo "HEADSCALE_HOST environment variable not provided. Attempting to determine pod IP for pod '$HEADPLANE_POD'..." | ||
| POD_IP=$(kubectl get pod -n $NAMESPACE $HEADPLANE_POD -o jsonpath='{.status.podIP}' --ignore-not-found) | ||
|
|
||
| if [[ -z "$POD_IP" ]]; then | ||
| POD_IP="127.0.0.1" | ||
| echo "Could not retrieve IP for pod '$HEADPLANE_POD'. Using default." | ||
| fi | ||
|
|
||
| HEADSCALE_HOST="http://$POD_IP" | ||
| echo "HEADSCALE_HOST set to: '$HEADSCALE_HOST'" | ||
| fi | ||
|
|
||
| API_KEY="" | ||
| echo "Checking for existing Kubernetes secret '$HEADSCALE_SECRET_NAME' in namespace '$NAMESPACE'..." | ||
|
|
||
| ENCODED_KEY_DATA=$(kubectl get secret "$HEADSCALE_SECRET_NAME" -n "$NAMESPACE" -o=jsonpath='{.data.api-key}' --ignore-not-found 2>/dev/null || echo "") | ||
|
|
||
| if [[ -n "$ENCODED_KEY_DATA" ]]; then | ||
| API_KEY=$(echo "$ENCODED_KEY_DATA" | base64 -d) | ||
| echo "Existing Headscale API Key found in secret '$HEADSCALE_SECRET_NAME'." | ||
|
|
||
| if check_api_key_validity "$API_KEY"; then | ||
| echo "Existing Headscale API Key is valid. No further action needed." | ||
| exit 0 | ||
| else | ||
| echo "Existing Headscale API Key is invalid. A new API Key will be generated." | ||
| fi | ||
| else | ||
| echo "Kubernetes secret '$HEADSCALE_SECRET_NAME' not found or does not contain a 'api-key' field. A new API Key will be generated." | ||
| fi | ||
|
|
||
| echo "Generating a new Headscale API Key by executing CLI inside 'headscale' container..." | ||
| API_KEY=$(kubectl exec -n "$NAMESPACE" -c headscale "$HEADPLANE_POD" -- headscale apikeys create -e 100y) | ||
|
|
||
| if [[ -z "$API_KEY" ]]; then | ||
| echo "Error: Failed to create a new API Key via 'headscale apikeys create' command." | ||
| exit 1 | ||
| fi | ||
| echo "Successfully generated a new Headscale API Key." | ||
|
|
||
| if kubectl get secret "$HEADSCALE_SECRET_NAME" -n "$NAMESPACE" &>/dev/null; then | ||
| echo "Updating existing secret '$HEADSCALE_SECRET_NAME' with the new API Key..." | ||
| kubectl patch secret "$HEADSCALE_SECRET_NAME" -n "$NAMESPACE" -p "{\"stringData\":{\"api-key\":\"$API_KEY\"}}" --type=merge | ||
| else | ||
| echo "Creating new secret '$HEADSCALE_SECRET_NAME' with the new API Key..." | ||
| kubectl create secret generic "$HEADSCALE_SECRET_NAME" -n "$NAMESPACE" --from-literal="api-key=$API_KEY" | ||
| fi | ||
| echo "Successfully ensured Headscale API Key in Kubernetes secret '$HEADSCALE_SECRET_NAME'." | ||
|
|
||
| echo "--- Performing final validation of the newly generated API Key ---" | ||
| if check_api_key_validity "$API_KEY"; then | ||
| echo "Final validation successful: The newly generated and stored Headscale API Key is valid." | ||
| exit 0 | ||
| else | ||
| echo "Final validation failed: The newly generated/stored Headscale API Key is NOT valid. Please investigate." | ||
| exit 1 | ||
| fi | ||
| {{- end }} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| apiVersion: v1 | ||
| kind: ConfigMap | ||
| metadata: | ||
| name: headplane | ||
| data: | ||
| config.yaml: | | ||
| server: | ||
| host: 0.0.0.0 | ||
| port: 3000 | ||
| cookie_secret_path: /var/secrets/headplane/server/{{ .Values.headplane.config.cookieSecret.secretKey }} | ||
| cookie_secure: true | ||
| headscale: | ||
| url: http://127.0.0.1:8080 | ||
| {{- if .Values.headscale.config.url }} | ||
| public_url: {{ .Values.headscale.config.url }} | ||
| {{- end }} | ||
| config_path: /etc/headscale/config.yaml | ||
| config_strict: true | ||
| integration: | ||
| kubernetes: | ||
| enabled: true | ||
| validate_manifest: true | ||
| pod_name: replaced-by-environment-variable | ||
| {{- if .Values.headplane.config.oidc.enabled }} | ||
| oidc: | ||
| issuer: {{ .Values.headplane.config.oidc.issuerUrl }} | ||
| client_id: {{ .Values.headplane.config.oidc.clientId }} | ||
| client_secret_path: /var/secrets/headplane/oidc/{{ .Values.headplane.config.oidc.clientSecret.secretKey }} | ||
| disable_api_key_login: {{ .Values.headplane.config.oidc.disableApiKeyLogin }} | ||
| token_endpoint_auth_method: {{ .Values.headplane.config.oidc.tokenEndpointAuthMethod }} | ||
| headscale_api_key_path: /var/secrets/headplane/headscale/{{ .Values.headplane.config.oidc.headscaleApiKey.secretKey }} | ||
| redirect_uri: {{ .Values.headplane.config.url }}/admin/oidc/callback | ||
| {{- end }} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| {{- if .Values.headplane.persistence.enabled }} | ||
| apiVersion: v1 | ||
| kind: PersistentVolumeClaim | ||
| metadata: | ||
| name: headplane-data | ||
| {{- with .Values.headplane.persistence.pvc.annotations }} | ||
| annotations: | ||
| {{- toYaml . | nindent 4 }} | ||
| {{- end }} | ||
| {{- with .Values.headplane.persistence.pvc.labels }} | ||
| labels: | ||
| {{- toYaml . | nindent 4 }} | ||
| {{- end }} | ||
| spec: | ||
| accessModes: | ||
| {{- toYaml .Values.headplane.persistence.pvc.accessModes | nindent 4 }} | ||
| resources: | ||
| requests: | ||
| storage: {{ .Values.headplane.persistence.pvc.storage }} | ||
| {{- if .Values.headplane.persistence.pvc.storageClassName }} | ||
| storageClassName: {{ .Values.headplane.persistence.pvc.storageClassName }} | ||
| {{- end }} | ||
| {{- end }} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
v1 chart when not even the app is v1 yet is awkward.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we match it to our SEMver?