Add per-cluster Tools Management and stop auto-installing side tools.
Introduce a catalog-driven Tools Management section under Clusters so admins can install/uninstall infrastructure tools per cluster: cert-manager (Helm/jetstack), ClusterIssuer (email + HTTP01 form, depends on cert-manager), and central Elasticsearch. Cluster creation no longer auto-installs Elastic or the cloudhost-node-cluster-dns DaemonSet; build bootstrap stays automatic. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,6 @@ import { ClusterAllocationLog } from './entities/cluster-allocation-log.entity';
|
||||
import { CreateClusterDto, UpdateClusterDto } from './dto/cluster.dto';
|
||||
import { CreateClusterPoolDto, UpdateClusterPoolDto } from './dto/cluster-pool.dto';
|
||||
import { ClusterStatus } from '../common/enums';
|
||||
import { ElasticsearchService } from '../kubernetes/elasticsearch.service';
|
||||
import { RegistryService } from '../kubernetes/registry.service';
|
||||
import { CreateApplicationDto } from '../applications/dto/application.dto';
|
||||
import { registerKubeconfigNoProxy } from '../common/kubernetes-proxy.util';
|
||||
@@ -53,8 +52,6 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy {
|
||||
private allocationLogsRepository: Repository<ClusterAllocationLog>,
|
||||
private dataSource: DataSource,
|
||||
private configService: ConfigService,
|
||||
@Inject(forwardRef(() => ElasticsearchService))
|
||||
private elasticsearchService: ElasticsearchService,
|
||||
@Inject(forwardRef(() => RegistryService))
|
||||
private registryService: RegistryService,
|
||||
) {}
|
||||
@@ -140,10 +137,8 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy {
|
||||
this.logger.error(`Failed to bootstrap cluster "${saved.name}": ${err.message}`);
|
||||
});
|
||||
|
||||
// Deploy central logging (Elasticsearch + Kibana) via Helm
|
||||
this.elasticsearchService.deploy(saved.id).catch((err) => {
|
||||
this.logger.error(`Failed to deploy central logging on "${saved.name}": ${err.message}`);
|
||||
});
|
||||
// Infrastructure tools (central logging, cert-manager, ClusterIssuer, …) are no
|
||||
// longer auto-installed. Install them on demand via Cluster → Tools Management.
|
||||
|
||||
this.recordHealthSnapshot(saved, {
|
||||
status: 'healthy',
|
||||
@@ -1005,100 +1000,11 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
await this.ensureNodeClusterDns(coreApi, appsApi);
|
||||
await this.ensureK3sRegistryMirrors(appsApi, registryUrl);
|
||||
|
||||
this.logger.log(`✅ Cluster bootstrap complete — registry: ${registryUrl}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kubelet/containerd pull images on the host network stack, which uses the node's
|
||||
* resolver (often systemd-resolved) — not pod DNS. Forward *.cluster.local to CoreDNS
|
||||
* so registry.cloudhost-builds.svc.cluster.local resolves during image pulls.
|
||||
*/
|
||||
private async ensureNodeClusterDns(
|
||||
coreApi: k8s.CoreV1Api,
|
||||
appsApi: k8s.AppsV1Api,
|
||||
): Promise<void> {
|
||||
const dsName = 'cloudhost-node-cluster-dns';
|
||||
const namespace = 'kube-system';
|
||||
let clusterDnsIp = '10.43.0.10';
|
||||
try {
|
||||
const dnsSvc = await coreApi.readNamespacedService('kube-dns', namespace);
|
||||
clusterDnsIp = dnsSvc.body.spec?.clusterIP || clusterDnsIp;
|
||||
} catch (err: any) {
|
||||
this.logger.warn(
|
||||
`Could not read kube-dns ClusterIP (${err.message}); using ${clusterDnsIp}`,
|
||||
);
|
||||
}
|
||||
|
||||
const configureScript = [
|
||||
'set -e',
|
||||
'CONF=/host/etc/systemd/resolved.conf.d/k8s-cluster-dns.conf',
|
||||
'mkdir -p /host/etc/systemd/resolved.conf.d',
|
||||
`cat > /tmp/k8s-cluster-dns.conf <<'EOF'`,
|
||||
'[Resolve]',
|
||||
`DNS=${clusterDnsIp}`,
|
||||
'Domains=~cluster.local',
|
||||
'EOF',
|
||||
'if [ ! -f "$CONF" ] || ! cmp -s /tmp/k8s-cluster-dns.conf "$CONF"; then',
|
||||
' cp /tmp/k8s-cluster-dns.conf "$CONF"',
|
||||
' echo "Updated k8s-cluster-dns.conf"',
|
||||
' if nsenter -t 1 -m -u -i -n -p -- systemctl is-active systemd-resolved >/dev/null 2>&1; then',
|
||||
' nsenter -t 1 -m -u -i -n -p -- systemctl restart systemd-resolved',
|
||||
' fi',
|
||||
'fi',
|
||||
'sleep infinity',
|
||||
].join('\n');
|
||||
|
||||
const daemonSet: k8s.V1DaemonSet = {
|
||||
metadata: {
|
||||
name: dsName,
|
||||
namespace,
|
||||
labels: { 'app.kubernetes.io/managed-by': 'cloudhost' },
|
||||
},
|
||||
spec: {
|
||||
selector: { matchLabels: { app: dsName } },
|
||||
template: {
|
||||
metadata: { labels: { app: dsName } },
|
||||
spec: {
|
||||
hostPID: true,
|
||||
tolerations: [{ operator: 'Exists' }],
|
||||
containers: [
|
||||
{
|
||||
name: 'configure',
|
||||
image: 'rancher/mirrored-library-busybox:1.36.1',
|
||||
command: ['/bin/sh', '-ec'],
|
||||
args: [configureScript],
|
||||
securityContext: { privileged: true },
|
||||
volumeMounts: [{ name: 'etc', mountPath: '/host/etc' }],
|
||||
},
|
||||
],
|
||||
volumes: [
|
||||
{
|
||||
name: 'etc',
|
||||
hostPath: { path: '/etc', type: 'Directory' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await appsApi.readNamespacedDaemonSet(dsName, namespace);
|
||||
await appsApi.replaceNamespacedDaemonSet(dsName, namespace, daemonSet);
|
||||
this.logger.log(`Updated DaemonSet "${dsName}" (cluster DNS ${clusterDnsIp})`);
|
||||
} catch (err: any) {
|
||||
if (err.statusCode === 404 || err.body?.code === 404) {
|
||||
await appsApi.createNamespacedDaemonSet(namespace, daemonSet);
|
||||
this.logger.log(`Created DaemonSet "${dsName}" (cluster DNS ${clusterDnsIp})`);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** In-cluster registry mirror for k3s/containerd (HTTP). Removes legacy external-registry DaemonSet if present. */
|
||||
private async ensureK3sRegistryMirrors(
|
||||
appsApi: k8s.AppsV1Api,
|
||||
|
||||
Reference in New Issue
Block a user