Add Argo CD Helm chart release
Validate and publish metrics-server chart / publish (push) Successful in 2s
Validate and publish metrics-server chart / validate (push) Successful in 3s
Validate and publish metrics-server chart / promote (push) Successful in 1s

This commit is contained in:
2026-10-19 08:43:32 +02:00
commit b3afdd3e04
12 changed files with 385 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
name: Validate and publish metrics-server chart
on:
push:
pull_request:
jobs:
validate:
runs-on: linux_amd64
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Helm
run: |
curl --fail --silent --show-error --location --output "${RUNNER_TEMP}/helm.tar.gz" https://get.helm.sh/helm-v4.2.2-linux-amd64.tar.gz
tar -xzf "${RUNNER_TEMP}/helm.tar.gz" -C "${RUNNER_TEMP}" linux-amd64/helm
mv "${RUNNER_TEMP}/linux-amd64/helm" "${RUNNER_TEMP}/helm"
chmod 0700 "${RUNNER_TEMP}/helm"
echo "${RUNNER_TEMP}" >> "$GITHUB_PATH"
- run: ./test.sh
publish:
if: gitea.event_name == 'push' && gitea.ref == 'refs/heads/main'
needs: validate
runs-on: linux_amd64
outputs:
chart_version: ${{ steps.chart.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Helm
run: |
curl --fail --silent --show-error --location --output "${RUNNER_TEMP}/helm.tar.gz" https://get.helm.sh/helm-v4.2.2-linux-amd64.tar.gz
tar -xzf "${RUNNER_TEMP}/helm.tar.gz" -C "${RUNNER_TEMP}" linux-amd64/helm
mv "${RUNNER_TEMP}/linux-amd64/helm" "${RUNNER_TEMP}/helm"
chmod 0700 "${RUNNER_TEMP}/helm"
echo "${RUNNER_TEMP}" >> "$GITHUB_PATH"
- id: chart
name: Derive chart version
env:
COMMIT_SHA: ${{ gitea.sha }}
run: |
set -euo pipefail
git fetch --tags origin main
test "$(git rev-parse origin/main)" = "$COMMIT_SHA"
description=$(git describe --tags --long --match 'v[0-9]*' --dirty)
case "$description" in *-dirty) echo 'Refusing dirty chart tree' >&2; exit 1 ;; esac
version=${description#v}
case "$version" in *-0-g*) version=${version%%-0-g*} ;; esac
echo "version=${version}" >> "$GITHUB_OUTPUT"
- name: Package and publish
env:
PACKAGE_USERNAME: ${{ secrets.PACKAGE_USERNAME }}
PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
CHART_VERSION: ${{ steps.chart.outputs.version }}
run: |
set -euo pipefail
test -n "$PACKAGE_USERNAME" && test -n "$PACKAGE_TOKEN"
helm lint chart
./test.sh
helm package chart --version "$CHART_VERSION" --destination "$RUNNER_TEMP"
curl --fail --silent --show-error --user "${PACKAGE_USERNAME}:${PACKAGE_TOKEN}" \
--request POST --upload-file "${RUNNER_TEMP}/metrics-server-${CHART_VERSION}.tgz" \
https://code.brunner.ninja/api/packages/feedc0de/helm/api/charts
promote:
if: gitea.event_name == 'push' && gitea.ref == 'refs/heads/main'
needs: publish
runs-on: linux_amd64
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Promote GitOps chart version
env:
GITOPS_TOKEN: ${{ secrets.GITOPS_TOKEN }}
CHART_VERSION: ${{ needs.publish.outputs.chart_version }}
COMMIT_SHA: ${{ gitea.sha }}
run: |
set -euo pipefail
test -n "$GITOPS_TOKEN"
git fetch origin main
test "$(git rev-parse origin/main)" = "$COMMIT_SHA"
auth=$(printf 'feedc0de:%s' "$GITOPS_TOKEN" | base64 | tr -d '\n')
git -c "http.extraheader=AUTHORIZATION: basic ${auth}" clone --depth=1 \
https://code.brunner.ninja/feedc0de/homelab-gitops.git "$RUNNER_TEMP/gitops"
unset auth
cd "$RUNNER_TEMP/gitops"
gitops_auth=$(printf 'feedc0de:%s' "$GITOPS_TOKEN" | base64 | tr -d '\n')
unset GITOPS_TOKEN
git config user.name feedc0de
git config user.email feedc0de@users.noreply.code.brunner.ninja
sed -i -E "0,/targetRevision: [^ ]+/s//targetRevision: ${CHART_VERSION}/" bootstrap/metrics-server.yaml
if git diff --quiet; then exit 0; fi
git add bootstrap/metrics-server.yaml
git commit -m "Deploy metrics-server chart ${CHART_VERSION}"
for attempt in 1 2 3; do
if git -c "http.extraheader=AUTHORIZATION: basic ${gitops_auth}" push origin HEAD:main; then exit 0; fi
git -c "http.extraheader=AUTHORIZATION: basic ${gitops_auth}" fetch origin main
git rebase origin/main
done
exit 1
+21
View File
@@ -0,0 +1,21 @@
# metrics-server Helm chart
Homelab-maintained adapter chart for the upstream Kubernetes Metrics Server.
It retains the `k8s-app=metrics-server` Deployment and Service selector used by
this cluster, while owning the aggregated APIService, RBAC, Service and
workload as one release.
The homelab configuration is managed by Argo CD in
[homelab-gitops](https://code.brunner.ninja/feedc0de/homelab-gitops/src/branch/main/apps/metrics-server).
The cluster currently requires `kubeletInsecureTls: true`; enable it only on
clusters whose kubelet certificates cannot be verified.
```sh
helm repo add brunner.ninja https://brunner.ninja/charts
helm repo update brunner.ninja
helm upgrade --install metrics-server brunner.ninja/metrics-server \
--namespace kube-system --wait --timeout 5m
```
Chart tags version this adapter; `appVersion` records the upstream image
version.
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v2
name: metrics-server
description: Homelab-maintained adapter chart for Kubernetes Metrics Server
type: application
version: 0.1.0
appVersion: "0.9.0"
+17
View File
@@ -0,0 +1,17 @@
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
name: v1beta1.metrics.k8s.io
labels:
app.kubernetes.io/name: metrics-server
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
group: metrics.k8s.io
groupPriorityMinimum: 100
insecureSkipTLSVerify: true
service:
name: metrics-server
namespace: {{ .Release.Namespace }}
port: 443
version: v1beta1
versionPriority: 100
+83
View File
@@ -0,0 +1,83 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: metrics-server
namespace: {{ .Release.Namespace }}
labels:
k8s-app: metrics-server
app.kubernetes.io/name: metrics-server
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
replicas: 1
revisionHistoryLimit: 10
strategy:
rollingUpdate:
maxUnavailable: 0
type: RollingUpdate
selector:
matchLabels:
k8s-app: metrics-server
template:
metadata:
labels:
k8s-app: metrics-server
app.kubernetes.io/name: metrics-server
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
serviceAccountName: metrics-server
priorityClassName: system-cluster-critical
nodeSelector:
kubernetes.io/os: linux
containers:
- name: metrics-server
image: {{ printf "%s:v%s" .Values.image.repository .Chart.AppVersion | quote }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
- --cert-dir=/tmp
- --secure-port=10250
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --kubelet-use-node-status-port
- --metric-resolution=15s
{{- if .Values.kubeletInsecureTls }}
- --kubelet-insecure-tls
{{- end }}
ports:
- name: https
containerPort: 10250
protocol: TCP
livenessProbe:
httpGet:
path: /livez
port: https
scheme: HTTPS
periodSeconds: 10
timeoutSeconds: 1
failureThreshold: 3
readinessProbe:
httpGet:
path: /readyz
port: https
scheme: HTTPS
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 1
failureThreshold: 3
resources:
requests:
cpu: 100m
memory: 200Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: tmp-dir
mountPath: /tmp
volumes:
- name: tmp-dir
emptyDir: {}
+76
View File
@@ -0,0 +1,76 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: system:metrics-server-aggregated-reader
labels:
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rbac.authorization.k8s.io/aggregate-to-view: "true"
rules:
- apiGroups: [metrics.k8s.io]
resources: [pods, nodes]
verbs: [get, list, watch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: system:metrics-server
labels:
app.kubernetes.io/name: metrics-server
app.kubernetes.io/instance: {{ .Release.Name }}
rules:
- apiGroups: [""]
resources: [nodes/metrics]
verbs: [get]
- apiGroups: [""]
resources: [pods, nodes, namespaces, configmaps]
verbs: [get, list, watch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: metrics-server:system:auth-delegator
labels:
app.kubernetes.io/name: metrics-server
app.kubernetes.io/instance: {{ .Release.Name }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: {{ .Release.Namespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: system:metrics-server
labels:
app.kubernetes.io/name: metrics-server
app.kubernetes.io/instance: {{ .Release.Name }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:metrics-server
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: {{ .Release.Namespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: metrics-server-auth-reader
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: metrics-server
app.kubernetes.io/instance: {{ .Release.Name }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: {{ .Release.Namespace }}
+16
View File
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: metrics-server
namespace: {{ .Release.Namespace }}
labels:
k8s-app: metrics-server
spec:
type: ClusterIP
ports:
- name: https
port: 443
protocol: TCP
targetPort: https
selector:
k8s-app: metrics-server
+8
View File
@@ -0,0 +1,8 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: metrics-server
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: metrics-server
app.kubernetes.io/instance: {{ .Release.Name }}
+17
View File
@@ -0,0 +1,17 @@
{
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"image": {
"type": "object",
"required": ["repository", "pullPolicy"],
"properties": {
"repository": {"type": "string", "minLength": 1},
"pullPolicy": {"type": "string", "enum": ["Always", "IfNotPresent", "Never"]}
},
"additionalProperties": false
},
"kubeletInsecureTls": {"type": "boolean"}
},
"additionalProperties": false
}
+5
View File
@@ -0,0 +1,5 @@
image:
repository: registry.k8s.io/metrics-server/metrics-server
pullPolicy: IfNotPresent
kubeletInsecureTls: false
Executable
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
set -eu
release=${RELEASE_NAME:-metrics-server}
namespace=${NAMESPACE:-kube-system}
helm repo add brunner.ninja https://brunner.ninja/charts --force-update
helm repo update brunner.ninja
if [ -n "${VALUES_FILE:-}" ]; then
helm upgrade --install "$release" brunner.ninja/metrics-server \
--namespace "$namespace" --values "$VALUES_FILE" --wait --timeout 5m
else
helm upgrade --install "$release" brunner.ninja/metrics-server \
--namespace "$namespace" --wait --timeout 5m
fi
Executable
+17
View File
@@ -0,0 +1,17 @@
#!/bin/sh
set -eu
chart_dir=$(CDPATH= cd -- "$(dirname -- "$0")/chart" && pwd)
workdir=$(mktemp -d)
trap 'rm -rf "$workdir"' EXIT
helm lint "$chart_dir"
helm template metrics-server "$chart_dir" --namespace kube-system \
--set kubeletInsecureTls=true >"$workdir/rendered.yaml"
grep -q 'kind: APIService' "$workdir/rendered.yaml"
grep -q 'name: v1beta1.metrics.k8s.io' "$workdir/rendered.yaml"
grep -q 'k8s-app: metrics-server' "$workdir/rendered.yaml"
grep -q -- '--kubelet-insecure-tls' "$workdir/rendered.yaml"
app_version=$(sed -n 's/^appVersion: *"\{0,1\}\([^" ]*\).*/\1/p' "$chart_dir/Chart.yaml")
image_repository=$(sed -n 's/^ repository: *//p' "$chart_dir/values.yaml")
grep -Fq "${image_repository}:v${app_version}" "$workdir/rendered.yaml"