From 7db663b7716996806f603d49f78dc7641e6747ff Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Tue, 10 Mar 2026 00:08:58 -0400 Subject: [PATCH 1/2] Add support for Headlamp dashboard for kubernetes; deprecate legacy kubernetes dashboard --- .../cluster/utils/KubernetesClusterUtil.java | 13 +++- .../main/resources/conf/k8s-control-node.yml | 19 +++++- .../util/create-kubernetes-binaries-iso.sh | 64 +++++++++++++++++-- ui/src/views/compute/KubernetesServiceTab.vue | 2 +- 4 files changed, 85 insertions(+), 13 deletions(-) diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/utils/KubernetesClusterUtil.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/utils/KubernetesClusterUtil.java index 00625f6e076d..023fc9ec7fad 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/utils/KubernetesClusterUtil.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/utils/KubernetesClusterUtil.java @@ -171,11 +171,20 @@ public static boolean isKubernetesClusterDashboardServiceRunning(final Kubernete // Check if dashboard service is up running. while (System.currentTimeMillis() < timeoutTime) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug(String.format("Checking dashboard service for the Kubernetes cluster: %s to come up", kubernetesCluster)); + LOGGER.debug(String.format("Checking dashboard service (Kubernetes Dashboard or Headlamp) for the Kubernetes cluster: %s to come up", kubernetesCluster)); } + // Check for Headlamp (new dashboard) in kube-system namespace + if (isKubernetesClusterAddOnServiceRunning(kubernetesCluster, ipAddress, port, user, sshKeyFile, "kube-system", "headlamp")) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info(String.format("Headlamp dashboard service for the Kubernetes cluster %s is in running state", kubernetesCluster)); + } + running = true; + break; + } + // For backward compatibility, check for Kubernetes Dashboard in kubernetes-dashboard namespace if (isKubernetesClusterAddOnServiceRunning(kubernetesCluster, ipAddress, port, user, sshKeyFile, "kubernetes-dashboard", "kubernetes-dashboard")) { if (LOGGER.isInfoEnabled()) { - LOGGER.info(String.format("Dashboard service for the Kubernetes cluster %s is in running state", kubernetesCluster)); + LOGGER.info(String.format("Kubernetes Dashboard service for the Kubernetes cluster %s is in running state", kubernetesCluster)); } running = true; break; diff --git a/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml b/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml index 70291dd1c35a..4db349ac778a 100644 --- a/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml +++ b/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml @@ -331,16 +331,29 @@ write_files: if [[ ${EXTERNAL_CNI_PLUGIN} == false ]]; then /opt/bin/kubectl apply -f ${K8S_CONFIG_SCRIPTS_COPY_DIR}/network.yaml fi - /opt/bin/kubectl apply -f ${K8S_CONFIG_SCRIPTS_COPY_DIR}/dashboard.yaml + if [ -f "${K8S_CONFIG_SCRIPTS_COPY_DIR}/headlamp.yaml" ]; then + echo "Installing Headlamp dashboard from ISO" + /opt/bin/kubectl apply -f ${K8S_CONFIG_SCRIPTS_COPY_DIR}/headlamp.yaml + elif [ -f "${K8S_CONFIG_SCRIPTS_COPY_DIR}/dashboard.yaml" ]; then + echo "Installing Kubernetes Dashboard from ISO" + /opt/bin/kubectl apply -f ${K8S_CONFIG_SCRIPTS_COPY_DIR}/dashboard.yaml + /opt/bin/kubectl create rolebinding admin-binding --role=admin --user=admin || true + /opt/bin/kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=admin || true + /opt/bin/kubectl create clusterrolebinding kubernetes-dashboard-ui --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:kubernetes-dashboard || true + else + echo "Warning: No dashboard YAML found in ISO (neither headlamp.yaml nor dashboard.yaml)" + fi rm -rf "${K8S_CONFIG_SCRIPTS_COPY_DIR}" else + ### Online installation - use Headlamp by default ### /opt/bin/kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(/opt/bin/kubectl version | base64 | tr -d '\n')" - /opt/bin/kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta6/aio/deploy/recommended.yaml + /opt/bin/kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/headlamp/v0.40.1/kubernetes-headlamp.yaml + /opt/bin/kubectl create serviceaccount headlamp-admin -n kube-system || true + /opt/bin/kubectl create clusterrolebinding headlamp-admin --clusterrole=cluster-admin --serviceaccount=kube-system:headlamp-admin || true fi /opt/bin/kubectl create rolebinding admin-binding --role=admin --user=admin || true /opt/bin/kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=admin || true - /opt/bin/kubectl create clusterrolebinding kubernetes-dashboard-ui --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:kubernetes-dashboard || true sudo touch /home/cloud/success echo "true" > /home/cloud/success diff --git a/scripts/util/create-kubernetes-binaries-iso.sh b/scripts/util/create-kubernetes-binaries-iso.sh index ebaf072771c5..9f781b47171e 100755 --- a/scripts/util/create-kubernetes-binaries-iso.sh +++ b/scripts/util/create-kubernetes-binaries-iso.sh @@ -19,8 +19,8 @@ set -e if [ $# -lt 6 ]; then - echo "Invalid input. Valid usage: ./create-kubernetes-binaries-iso.sh OUTPUT_PATH KUBERNETES_VERSION CNI_VERSION CRICTL_VERSION WEAVENET_NETWORK_YAML_CONFIG DASHBOARD_YAML_CONFIG BUILD_NAME [ARCH] [ETCD_VERSION]" - echo "eg: ./create-kubernetes-binaries-iso.sh ./ 1.11.4 0.7.1 1.11.1 https://github.com/weaveworks/weave/releases/download/latest_release/weave-daemonset-k8s-1.11.yaml https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.0/src/deploy/recommended/kubernetes-dashboard.yaml setup-v1.11.4 amd64" + echo "Invalid input. Valid usage: ./create-kubernetes-binaries-iso.sh OUTPUT_PATH KUBERNETES_VERSION CNI_VERSION CRICTL_VERSION WEAVENET_NETWORK_YAML_CONFIG HEADLAMP_DASHBOARD_VERSION BUILD_NAME [ARCH] [ETCD_VERSION]" + echo "eg: ./create-kubernetes-binaries-iso.sh ./ 1.11.4 0.7.1 1.11.1 https://github.com/weaveworks/weave/releases/download/latest_release/weave-daemonset-k8s-1.11.yaml 0.40.1 setup-v1.11.4 amd64" exit 1 fi @@ -96,10 +96,60 @@ echo "Downloading network config ${NETWORK_CONFIG_URL}" network_conf_file="${working_dir}/network.yaml" curl -sSL ${NETWORK_CONFIG_URL} -o ${network_conf_file} -DASHBORAD_CONFIG_URL="${6}" -echo "Downloading dashboard config ${DASHBORAD_CONFIG_URL}" -dashboard_conf_file="${working_dir}/dashboard.yaml" -curl -sSL ${DASHBORAD_CONFIG_URL} -o ${dashboard_conf_file} +HEADLAMP_DASHBOARD_VERSION="${6}" +HEADLAMP_DASHBOARD_URL="https://raw.githubusercontent.com/kubernetes-sigs/headlamp/v${HEADLAMP_DASHBOARD_VERSION}/kubernetes-headlamp.yaml" +echo "Downloading Headlamp manifest from ${HEADLAMP_DASHBOARD_URL}" +headlamp_conf_file="${working_dir}/headlamp.yaml" +curl -sSL ${HEADLAMP_DASHBOARD_URL} -o ${headlamp_conf_file} + +# Patch the Headlamp manifest to add missing components +echo "Patching Headlamp manifest with missing ServiceAccount and ClusterRoleBinding..." + +if ! grep -q "kind: ServiceAccount" ${headlamp_conf_file}; then + echo "Adding missing ServiceAccount to Headlamp manifest" + cat > ${headlamp_conf_file}.tmp << 'EOF' +--- +# ServiceAccount for Headlamp (added by CloudStack) +kind: ServiceAccount +apiVersion: v1 +metadata: + name: headlamp-admin + namespace: kube-system +--- +# ClusterRoleBinding to grant cluster-admin permissions to Headlamp (added by CloudStack) +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: headlamp-admin +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: headlamp-admin + namespace: kube-system +--- +EOF + cat ${headlamp_conf_file} >> ${headlamp_conf_file}.tmp + mv ${headlamp_conf_file}.tmp ${headlamp_conf_file} +fi + +if grep -q "kind: Deployment" ${headlamp_conf_file} && ! grep -q "serviceAccountName:" ${headlamp_conf_file}; then + echo "Adding serviceAccountName to Headlamp Deployment" + awk '/kind: Deployment/,0 { + if (/^ spec:$/ && !found) { + print + print " serviceAccountName: headlamp-admin" + found=1 + next + } + } + {print}' ${headlamp_conf_file} > ${headlamp_conf_file}.tmp + mv ${headlamp_conf_file}.tmp ${headlamp_conf_file} +fi + +echo "Headlamp manifest patched successfully" # TODO : Change the url once merged AUTOSCALER_URL="https://raw.githubusercontent.com/kubernetes/autoscaler/master/cluster-autoscaler/cloudprovider/cloudstack/examples/cluster-autoscaler-standard.yaml" @@ -135,7 +185,7 @@ mkdir -p "${working_dir}/docker" output=`${k8s_dir}/kubeadm config images list --kubernetes-version=${RELEASE}` # Don't forget about the yaml images ! -for i in ${network_conf_file} ${dashboard_conf_file} +for i in ${network_conf_file} ${headlamp_conf_file} do images=`grep "image:" $i | cut -d ':' -f2- | tr -d ' ' | tr -d "'"` output=`printf "%s\n" ${output} ${images}` diff --git a/ui/src/views/compute/KubernetesServiceTab.vue b/ui/src/views/compute/KubernetesServiceTab.vue index fc8f9c60213a..1d34a797417c 100644 --- a/ui/src/views/compute/KubernetesServiceTab.vue +++ b/ui/src/views/compute/KubernetesServiceTab.vue @@ -87,7 +87,7 @@

{{ $t('label.token.for.dashboard.login') }}

- kubectl --kubeconfig /custom/path/kube.conf describe secret $(kubectl --kubeconfig /custom/path/kube.conf get secrets -n kubernetes-dashboard | grep kubernetes-dashboard-token | awk '{print $1}') -n kubernetes-dashboard + kubectl --kubeconfig /custom/path/kube.conf describe secret $(kubectl --kubeconfig /custom/path/kube.conf get secrets -n kube-system | grep headlamp-admin | awk '{print $1}') -n kube-system

From 887317eb1b7807802bdc4d219a7af6f4b597dbe2 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Tue, 10 Mar 2026 13:21:21 -0400 Subject: [PATCH 2/2] follow similar steps to legacy k8s dashboard and update access notes --- .../util/create-kubernetes-binaries-iso.sh | 49 ------------------- ui/src/views/compute/KubernetesServiceTab.vue | 44 ++++++++++++++--- 2 files changed, 37 insertions(+), 56 deletions(-) diff --git a/scripts/util/create-kubernetes-binaries-iso.sh b/scripts/util/create-kubernetes-binaries-iso.sh index 9f781b47171e..00234f205114 100755 --- a/scripts/util/create-kubernetes-binaries-iso.sh +++ b/scripts/util/create-kubernetes-binaries-iso.sh @@ -102,55 +102,6 @@ echo "Downloading Headlamp manifest from ${HEADLAMP_DASHBOARD_URL}" headlamp_conf_file="${working_dir}/headlamp.yaml" curl -sSL ${HEADLAMP_DASHBOARD_URL} -o ${headlamp_conf_file} -# Patch the Headlamp manifest to add missing components -echo "Patching Headlamp manifest with missing ServiceAccount and ClusterRoleBinding..." - -if ! grep -q "kind: ServiceAccount" ${headlamp_conf_file}; then - echo "Adding missing ServiceAccount to Headlamp manifest" - cat > ${headlamp_conf_file}.tmp << 'EOF' ---- -# ServiceAccount for Headlamp (added by CloudStack) -kind: ServiceAccount -apiVersion: v1 -metadata: - name: headlamp-admin - namespace: kube-system ---- -# ClusterRoleBinding to grant cluster-admin permissions to Headlamp (added by CloudStack) -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: headlamp-admin -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cluster-admin -subjects: - - kind: ServiceAccount - name: headlamp-admin - namespace: kube-system ---- -EOF - cat ${headlamp_conf_file} >> ${headlamp_conf_file}.tmp - mv ${headlamp_conf_file}.tmp ${headlamp_conf_file} -fi - -if grep -q "kind: Deployment" ${headlamp_conf_file} && ! grep -q "serviceAccountName:" ${headlamp_conf_file}; then - echo "Adding serviceAccountName to Headlamp Deployment" - awk '/kind: Deployment/,0 { - if (/^ spec:$/ && !found) { - print - print " serviceAccountName: headlamp-admin" - found=1 - next - } - } - {print}' ${headlamp_conf_file} > ${headlamp_conf_file}.tmp - mv ${headlamp_conf_file}.tmp ${headlamp_conf_file} -fi - -echo "Headlamp manifest patched successfully" - # TODO : Change the url once merged AUTOSCALER_URL="https://raw.githubusercontent.com/kubernetes/autoscaler/master/cluster-autoscaler/cloudprovider/cloudstack/examples/cluster-autoscaler-standard.yaml" echo "Downloading kubernetes cluster autoscaler ${AUTOSCALER_URL}" diff --git a/ui/src/views/compute/KubernetesServiceTab.vue b/ui/src/views/compute/KubernetesServiceTab.vue index 1d34a797417c..3c45feea063f 100644 --- a/ui/src/views/compute/KubernetesServiceTab.vue +++ b/ui/src/views/compute/KubernetesServiceTab.vue @@ -66,32 +66,62 @@ +

Note: CloudStack Kubernetes clusters use Headlamp dashboard (deployed in kube-system namespace). For backward compatibility with older clusters using Kubernetes Dashboard, please check your cluster configuration.

- {{ $t('label.run.proxy.locally') }}

- kubectl --kubeconfig /custom/path/kube.conf proxy + Access Headlamp Dashboard (new clusters)

+ Step 1: Run port-forward command:
+ kubectl --kubeconfig /custom/path/kube.conf port-forward -n kube-system service/headlamp 8080:80

+ Step 2: Open in your browser:
+ http://localhost:8080

- {{ $t('label.open.url') }}

+ Access Kubernetes Dashboard (legacy clusters)

+ Step 1: {{ $t('label.run.proxy.locally') }}
+ kubectl --kubeconfig /custom/path/kube.conf proxy

+ Step 2: {{ $t('label.open.url') }}
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

+

+ Create Access Token for Headlamp (new clusters) +

- + +

+

{{ $t('label.token.for.dashboard.login') }}:

+ kubectl --kubeconfig /custom/path/kube.conf describe secret headlamp-admin-token -n kube-system +
+ +

+ Create Access Token for Kubernetes Dashboard (legacy clusters) +

+

+ +

+

{{ $t('label.token.for.dashboard.login') }}:

+ kubectl --kubeconfig /custom/path/kube.conf describe secret kubernetes-dashboard-token -n kubernetes-dashboard

- {{ $t('label.token.for.dashboard.login') }}

- kubectl --kubeconfig /custom/path/kube.conf describe secret $(kubectl --kubeconfig /custom/path/kube.conf get secrets -n kube-system | grep headlamp-admin | awk '{print $1}') -n kube-system + Important Notes:
+ • Port-forwarding is recommended for Headlamp - simpler and more reliable than kubectl proxy
+ • Token is only needed if accessing Headlamp via NodePort or LoadBalancer with external access
+ • For Kubernetes 1.24+, service account tokens are no longer auto-generated - use the Secret resource shown above or kubectl create token command
+ • Cluster-admin role grants full control - use with caution and only for trusted administrators
+ • Keep the port-forward command running while using the dashboard (press Ctrl+C to stop)

-

{{ $t('label.more.access.dashboard.ui') }}, https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#accessing-the-dashboard-ui

+

{{ $t('label.more.access.dashboard.ui') }}: + Headlamp Documentation | + Kubernetes Dashboard (Legacy) +