Use in-cluster registry for builds and deploys; improve logging and cluster ops.

Remove external registry Ingress (repo.3fase.ir) and route Kaniko push and app pulls through the internal ClusterIP registry. Add RegistryService, ensure StorageClass and pull secrets on deploy, make Elasticsearch install/repair more resilient, and add per-cluster Deploy Elastic controls in admin UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
keyhan
2026-05-26 19:15:06 +03:30
parent 1ade52825c
commit d7594df9a0
20 changed files with 547 additions and 237 deletions
+27 -26
View File
@@ -21,6 +21,7 @@ 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';
@@ -54,6 +55,8 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy {
private configService: ConfigService,
@Inject(forwardRef(() => ElasticsearchService))
private elasticsearchService: ElasticsearchService,
@Inject(forwardRef(() => RegistryService))
private registryService: RegistryService,
) {}
onModuleInit(): void {
@@ -835,9 +838,9 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy {
const coreApi = kc.makeApiClient(k8s.CoreV1Api);
const appsApi = kc.makeApiClient(k8s.AppsV1Api);
const buildNs = this.configService.get<string>('build.namespace') || 'cloudhost-builds';
const buildNs = this.registryService.getBuildNamespace();
const saName = this.configService.get<string>('build.serviceAccount') || 'kaniko-builder';
const registryUrl = this.configService.get<string>('registry.url') || `registry.${buildNs}.svc.cluster.local:5000`;
const registryUrl = this.registryService.getRegistryUrl();
this.logger.log(`Bootstrapping cluster — namespace: ${buildNs}`);
@@ -908,9 +911,7 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy {
name: 'registry',
image: 'registry:2',
ports: [{ containerPort: 5000 }],
env: [
{ name: 'REGISTRY_STORAGE_DELETE_ENABLED', value: 'true' },
],
env: [{ name: 'REGISTRY_STORAGE_DELETE_ENABLED', value: 'true' }],
volumeMounts: [{
name: 'registry-data',
mountPath: '/var/lib/registry',
@@ -934,7 +935,7 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy {
}
}
// ── 5. Registry ClusterIP Service (for Kaniko to push) ────────
// ── 5. Registry ClusterIP Service (Kaniko push + app pull) ───
const registrySvcName = 'registry';
try {
await coreApi.readNamespacedService(registrySvcName, buildNs);
@@ -955,7 +956,8 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy {
}
}
// ── 6. Registry NodePort Service (for kubelet to pull) ───────
// ── 6. Registry NodePort Service (optional host access :30500)
const registryNodePort = 30500;
const registryNodePortName = 'registry-nodeport';
try {
await coreApi.readNamespacedService(registryNodePortName, buildNs);
@@ -967,10 +969,15 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy {
spec: {
type: 'NodePort',
selector: { app: 'registry' },
ports: [{ port: 5000, targetPort: 5000 as any, nodePort: 30500, protocol: 'TCP' }],
ports: [{
port: 5000,
targetPort: 5000 as any,
nodePort: registryNodePort,
protocol: 'TCP',
}],
},
});
this.logger.log(`Created Registry NodePort Service (30500 → 5000)`);
this.logger.log(`Created Registry NodePort Service (${registryNodePort} → 5000)`);
} else {
throw err;
}
@@ -978,33 +985,27 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy {
// ── 7. registry-credentials Secret (docker config for Kaniko) ─
const registrySecretName = 'registry-credentials';
const dockerConfig = this.registryService.buildDockerConfigJson();
const kanikoRegistrySecret: k8s.V1Secret = {
metadata: { name: registrySecretName, namespace: buildNs },
type: 'kubernetes.io/dockerconfigjson',
data: {
'.dockerconfigjson': Buffer.from(dockerConfig).toString('base64'),
},
};
try {
await coreApi.readNamespacedSecret(registrySecretName, buildNs);
this.logger.log(`Secret "${registrySecretName}" already exists`);
await coreApi.replaceNamespacedSecret(registrySecretName, buildNs, kanikoRegistrySecret);
} catch (err: any) {
if (err.statusCode === 404 || err.body?.code === 404) {
// Parse registry host (without port path) for the docker config
const dockerConfig = JSON.stringify({
auths: {
[registryUrl]: { auth: '' },
[`registry.${buildNs}.svc.cluster.local:5000`]: { auth: '' },
'localhost:30500': { auth: '' },
},
});
await coreApi.createNamespacedSecret(buildNs, {
metadata: { name: registrySecretName, namespace: buildNs },
type: 'kubernetes.io/dockerconfigjson',
data: {
'.dockerconfigjson': Buffer.from(dockerConfig).toString('base64'),
},
});
await coreApi.createNamespacedSecret(buildNs, kanikoRegistrySecret);
this.logger.log(`Created registry-credentials Secret`);
} else {
throw err;
}
}
this.logger.log(`✅ Cluster bootstrap complete — build infrastructure ready`);
this.logger.log(`✅ Cluster bootstrap complete — registry: ${registryUrl}`);
}
private parseCpuToMillicores(cpu: string): number {