Fix app image pulls and Harbor kubelet auth for user workloads.
Route k3s registry mirrors through harbor-core ClusterIP with hostname-only auth keys, use HTTP EXT_ENDPOINT so OAuth tokens work on port 80, extend deploy readiness timeout, and harden Kaniko build/dockerfile fallbacks. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -870,7 +870,7 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy {
|
||||
|
||||
const buildNs = this.registryService.getBuildNamespace();
|
||||
const saName = this.configService.get<string>('build.serviceAccount') || 'kaniko-builder';
|
||||
const registryUrl = this.registryService.getRegistryUrl();
|
||||
const registryHost = this.registryService.getRegistryHost();
|
||||
|
||||
this.logger.log(`Bootstrapping cluster — namespace: ${buildNs}`);
|
||||
|
||||
@@ -1100,13 +1100,17 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
await this.ensureK3sRegistryMirrors(appsApi, registryUrl);
|
||||
await this.ensureK3sRegistryMirrors(coreApi, appsApi, registryHost);
|
||||
|
||||
this.logger.log(`✅ Cluster bootstrap complete — registry: ${registryUrl}`);
|
||||
this.logger.log(`✅ Cluster bootstrap complete — registry: ${registryHost}`);
|
||||
}
|
||||
|
||||
/** In-cluster registry mirror for k3s/containerd (HTTP). Removes legacy external-registry DaemonSet if present. */
|
||||
private async ensureK3sRegistryMirrors(appsApi: k8s.AppsV1Api, registryUrl: string): Promise<void> {
|
||||
private async ensureK3sRegistryMirrors(
|
||||
coreApi: k8s.CoreV1Api,
|
||||
appsApi: k8s.AppsV1Api,
|
||||
registryHost: string,
|
||||
): Promise<void> {
|
||||
const namespace = 'kube-system';
|
||||
const legacyDs = 'cloudhost-k3s-registry-config';
|
||||
try {
|
||||
@@ -1120,28 +1124,25 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy {
|
||||
|
||||
const { username, password } = this.registryService.getRegistryCredentials();
|
||||
const dsName = 'cloudhost-k3s-registry-mirrors';
|
||||
// The mirror endpoint must be reachable by the node's containerd, which does
|
||||
// NOT use cluster DNS — so we point it at the registry NodePort on loopback
|
||||
// (http://127.0.0.1:<nodePort>) instead of the in-cluster service DNS name.
|
||||
// Otherwise image pulls break whenever node-level resolution of
|
||||
// *.svc.cluster.local is unavailable (e.g. right after a node restart).
|
||||
const registryNodePort = 30500;
|
||||
const nodePortHost = `127.0.0.1:${registryNodePort}`;
|
||||
// containerd on the node does not use cluster DNS — mirror via ClusterIP (Harbor)
|
||||
// or loopback NodePort (legacy in-cluster registry).
|
||||
const mirrorEndpoint = await this.resolveK3sRegistryMirrorEndpoint(coreApi);
|
||||
const mirrorHost = mirrorEndpoint.replace(/^https?:\/\//, '');
|
||||
const configureScript = [
|
||||
'set -e',
|
||||
'REG=/host/etc/rancher/k3s/registries.yaml',
|
||||
'mkdir -p /host/etc/rancher/k3s',
|
||||
'cat > /tmp/cloudhost-registries.yaml <<EOFREG',
|
||||
'mirrors:',
|
||||
` "${registryUrl}":`,
|
||||
` "${registryHost}":`,
|
||||
' endpoint:',
|
||||
` - "http://${nodePortHost}"`,
|
||||
` - "${mirrorEndpoint}"`,
|
||||
'configs:',
|
||||
` "${registryUrl}":`,
|
||||
` "${registryHost}":`,
|
||||
' auth:',
|
||||
` username: ${JSON.stringify(username)}`,
|
||||
` password: ${JSON.stringify(password)}`,
|
||||
` "${nodePortHost}":`,
|
||||
` "${mirrorHost}":`,
|
||||
' auth:',
|
||||
` username: ${JSON.stringify(username)}`,
|
||||
` password: ${JSON.stringify(password)}`,
|
||||
@@ -1206,6 +1207,36 @@ export class ClustersService implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* containerd on nodes cannot resolve *.svc.cluster.local — use ClusterIP for
|
||||
* Harbor (harbor-core HTTP) or legacy registry NodePort on loopback.
|
||||
*/
|
||||
private async resolveK3sRegistryMirrorEndpoint(coreApi: k8s.CoreV1Api): Promise<string> {
|
||||
const pushUrl = this.registryService.getRegistryPushUrl();
|
||||
const platformNs = this.configService.get<string>('platform.namespace') || 'cloudhost';
|
||||
const harborCoreService = this.configService.get<string>('registry.harborCoreService') || 'harbor-core';
|
||||
|
||||
if (pushUrl.includes('harbor-registry')) {
|
||||
try {
|
||||
const svc = await coreApi.readNamespacedService({
|
||||
name: harborCoreService,
|
||||
namespace: platformNs,
|
||||
});
|
||||
const clusterIp = svc.spec?.clusterIP;
|
||||
if (clusterIp) {
|
||||
return `http://${clusterIp}`;
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.logger.warn(
|
||||
`Could not resolve ${harborCoreService} ClusterIP for k3s mirror: ${err.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const registryNodePort = 30500;
|
||||
return `http://127.0.0.1:${registryNodePort}`;
|
||||
}
|
||||
|
||||
private parseCpuToMillicores(cpu: string): number {
|
||||
if (!cpu || cpu === '0') return 0;
|
||||
if (cpu.endsWith('n')) return parseFloat(cpu) / 1_000_000;
|
||||
|
||||
Reference in New Issue
Block a user