Headlamp는 쿠버네티스 표준 API 및 리소스(CRD 포함)를 기본으로 탐색할 수 있으며, React 기반 플러그인(Plugin)을 통해 사이드바에 전용 메뉴를 생성하고 CNPG 및 StarRocks 전용 상태 대시보드를 구성할 수 있습니다.
Headlamp의 클러스터 배포 방법과 전용 플러그인 구현 소스코드를 정리해 드립니다.
Helm을 사용하여 쿠버네티스 클러스터 내부에 배포합니다.
# 1. Helm Repo 추가
helm repo add headlamp https://headlamp-k8s.github.io/headlamp/
helm repo update
# 2. In-Cluster 배포 (Ingress 및 플러그인 Volume 설정 포함)
helm install headlamp headlamp/headlamp \
--namespace headlamp \
--create-namespace \
--set ingress.enabled=true \
--set ingress.hosts[0].host=headlamp.internal.example.com \
--set ingress.hosts[0].paths[0].path=/ \
--set ingress.hosts[0].paths[0].type=ImplementationSpecific \
--set config.pluginsDir=/headlamp/plugins
apiVersion: v1
kind: ServiceAccount
metadata:
name: headlamp-admin
namespace: headlamp
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: headlamp-admin-binding
subjects:
- kind: ServiceAccount
name: headlamp-admin
namespace: headlamp
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
토큰 생성:
kubectl create token headlamp-admin -n headlamp --duration=24h후 웹 UI 로그인 시 입력.
Headlamp 플러그인은 @kinvolk/headlamp-plugin/lib SDK를 사용하여 작성하며, K8s Custom Resource를 비동기로 조회해 React 컴포넌트로 렌더링합니다.
npx @kinvolk/headlamp-plugin create lakehouse-monitor
cd lakehouse-monitor
src/index.tsx (전체 소스코드)사이드바에 "Data Lakehouse" 탭을 추가하고, CNPG Cluster와 StarRocksCluster의 인스턴스별 상태(Phase, Primary, Replicas, FE/BE 노드 수)를 카드로 집계하는 플러그인 코드입니다.
import {
registerSidebarEntry,
registerRoute,
K8s,
CommonComponents
} from '@kinvolk/headlamp-plugin/lib';
import React from 'react';
const { SectionBox, SimpleTable, StatusLabel } = CommonComponents;
// 1. K8s Custom Resource Definition 매핑
const CNPGCluster = K8s.ResourceClasses.makeCustomResourceClass({
apiInfo: [{ group: 'postgresql.cnpg.io', version: 'v1', resource: 'clusters' }],
isNamespaced: true,
});
const StarRocksCluster = K8s.ResourceClasses.makeCustomResourceClass({
apiInfo: [{ group: 'starrocks.com', version: 'v1', resource: 'starrocksclusters' }],
isNamespaced: true,
});
// 2. Lakehouse 상태 대시보드 메인 컴포넌트
function LakehouseDashboard() {
const [cnpgList, setCnpgList] = React.useState<any[]>([]);
const [srList, setSrList] = React.useState<any[]>([]);
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
Promise.all([
CNPGCluster.apiEndpoint.get('/apis/postgresql.cnpg.io/v1/clusters'),
StarRocksCluster.apiEndpoint.get('/apis/starrocks.com/v1/starrocksclusters'),
])
.then(([cnpgRes, srRes]) => {
setCnpgList(cnpgRes?.items || []);
setSrList(srRes?.items || []);
})
.catch((err) => console.error('Failed to fetch Lakehouse CRDs:', err))
.finally(() => setLoading(false));
}, []);
if (loading) {
return <div>Loading Lakehouse Clusters...</div>;
}
return (
<div>
{/* CloudNativePG (PostgreSQL) Status Section */}
<SectionBox title={`CloudNativePG Clusters (${cnpgList.length})`}>
<SimpleTable
columns={[
{ label: 'Namespace', getter: (item: any) => item.metadata.namespace },
{ label: 'Name', getter: (item: any) => item.metadata.name },
{
label: 'Phase / Status',
getter: (item: any) => {
const phase = item.status?.phase || 'Unknown';
const statusType = phase === 'Cluster in healthy state' ? 'success' : 'warning';
return <StatusLabel status={statusType}>{phase}</StatusLabel>;
}
},
{ label: 'Primary Pod', getter: (item: any) => item.status?.currentPrimary || '-' },
{
label: 'Instances (Ready/Total)',
getter: (item: any) => `${item.status?.readyInstances || 0} / ${item.status?.instances || 0}`
},
]}
data={cnpgList}
/>
</SectionBox>
{/* StarRocks Status Section */}
<SectionBox title={`StarRocks Clusters (${srList.length})`}>
<SimpleTable
columns={[
{ label: 'Namespace', getter: (item: any) => item.metadata.namespace },
{ label: 'Name', getter: (item: any) => item.metadata.name },
{
label: 'Phase',
getter: (item: any) => {
const phase = item.status?.phase || 'Running';
const statusType = phase === 'Running' ? 'success' : 'error';
return <StatusLabel status={statusType}>{phase}</StatusLabel>;
}
},
{
label: 'FE Replicas (Ready/Req)',
getter: (item: any) =>
`${item.status?.starRocksFeStatus?.readyReplicas || 0} / ${item.spec?.starRocksFeSpec?.replicas || 0}`
},
{
label: 'BE Replicas (Ready/Req)',
getter: (item: any) =>
`${item.status?.starRocksBeStatus?.readyReplicas || 0} / ${item.spec?.starRocksBeSpec?.replicas || 0}`
},
]}
data={srList}
/>
</SectionBox>
</div>
);
}
// 3. 사이드바 및 라우트 등록
registerRoute({
path: '/lakehouse',
sidebar: 'lakehouse',
name: 'Data Lakehouse',
exact: true,
component: () => <LakehouseDashboard />,
});
registerSidebarEntry({
name: 'lakehouse',
label: 'Lakehouse DBs',
url: '/lakehouse',
icon: 'mdi:database-outline',
});
npm run build
# 빌드 결과물: dist/main.js
main.js를 ConfigMap으로 만들어 Pod의 /headlamp/plugins/lakehouse-monitor/main.js 경로에 마운트합니다.apiVersion: v1
kind: ConfigMap
metadata:
name: headlamp-lakehouse-plugin
namespace: headlamp
data:
main.js: |
# dist/main.js 내용 붙여넣기
Helm values.yaml에 볼륨 마운트 추가:
extraVolumes:
- name: lakehouse-plugin
configMap:
name: headlamp-lakehouse-plugin
extraVolumeMounts:
- name: lakehouse-plugin
mountPath: /headlamp/plugins/lakehouse-monitor/main.js
subPath: main.js