diff --git a/dist/index.js b/dist/index.js index 8581817d..b07d9516 100644 Binary files a/dist/index.js and b/dist/index.js differ diff --git a/dist/index.js.map b/dist/index.js.map index 71cf900b..73be15ed 100644 Binary files a/dist/index.js.map and b/dist/index.js.map differ diff --git a/src/model/cloud-runner/providers/k8s/index.ts b/src/model/cloud-runner/providers/k8s/index.ts index 83f85163..ec619e42 100644 --- a/src/model/cloud-runner/providers/k8s/index.ts +++ b/src/model/cloud-runner/providers/k8s/index.ts @@ -16,11 +16,14 @@ import { ProviderResource } from '../provider-resource'; import { ProviderWorkflow } from '../provider-workflow'; import { RemoteClientLogger } from '../../remote-client/remote-client-logger'; import { KubernetesRole } from './kubernetes-role'; +import KubernetesLogService from './kubernetes-log-service'; +import { CloudRunnerSystem } from '../../services/core/cloud-runner-system'; class Kubernetes implements ProviderInterface { public static Instance: Kubernetes; public kubeConfig!: k8s.KubeConfig; public kubeClient!: k8s.CoreV1Api; + public kubeClientApps!: k8s.AppsV1Api; public kubeClientBatch!: k8s.BatchV1Api; public rbacAuthorizationV1Api!: k8s.RbacAuthorizationV1Api; public buildGuid: string = ''; @@ -40,6 +43,7 @@ class Kubernetes implements ProviderInterface { this.kubeConfig = new k8s.KubeConfig(); this.kubeConfig.loadFromDefault(); this.kubeClient = this.kubeConfig.makeApiClient(k8s.CoreV1Api); + this.kubeClientApps = this.kubeConfig.makeApiClient(k8s.AppsV1Api); this.kubeClientBatch = this.kubeConfig.makeApiClient(k8s.BatchV1Api); this.rbacAuthorizationV1Api = this.kubeConfig.makeApiClient(k8s.RbacAuthorizationV1Api); this.namespace = 'default'; @@ -47,13 +51,17 @@ class Kubernetes implements ProviderInterface { } async PushLogUpdate(logs: string) { - const body: k8s.V1ConfigMap = new k8s.V1ConfigMap(); - body.data = {}; - body.data['logs'] = logs; - body.metadata = { name: `${this.jobName}-logs`, namespace: this.namespace, labels: { app: 'unity-builder' } }; - RemoteClientLogger.log(`Pushing to Kubernetes ConfigMap`); - await this.kubeClient.createNamespacedConfigMap(this.namespace, body); - RemoteClientLogger.log(`Pushed logs to Kubernetes ConfigMap`); + // push logs to nginx file server via 'LOG_SERVICE_IP' env var + const ip = process.env[`LOG_SERVICE_IP`]; + if (ip === undefined) { + RemoteClientLogger.logWarning(`LOG_SERVICE_IP not set, skipping log push`); + + return; + } + const url = `http://${ip}/api/log`; + RemoteClientLogger.log(`Pushing logs to ${url}`); + const response = await CloudRunnerSystem.Run(`curl -X POST -d "${logs}" ${url}`, false, true); + RemoteClientLogger.log(`Pushed logs to ${url} ${response}`); } async listResources(): Promise { @@ -232,6 +240,7 @@ class Kubernetes implements ProviderInterface { ) { for (let index = 0; index < 3; index++) { try { + const ip = await KubernetesLogService.createLogDeployment(this.namespace, this.kubeClientApps, this.kubeClient); const jobSpec = KubernetesJobSpecFactory.getJobSpec( commands, image, @@ -246,9 +255,12 @@ class Kubernetes implements ProviderInterface { this.jobName, k8s, this.containerName, + ip, ); await new Promise((promise) => setTimeout(promise, 15000)); - await KubernetesRole.createRole(this.serviceAccountName, this.namespace, this.rbacAuthorizationV1Api); + + // await KubernetesRole.createRole(this.serviceAccountName, this.namespace, this.rbacAuthorizationV1Api); + const result = await this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec); CloudRunnerLogger.log(`Build job created`); await new Promise((promise) => setTimeout(promise, 5000)); diff --git a/src/model/cloud-runner/providers/k8s/kubernetes-job-spec-factory.ts b/src/model/cloud-runner/providers/k8s/kubernetes-job-spec-factory.ts index af488c5e..01ca5687 100644 --- a/src/model/cloud-runner/providers/k8s/kubernetes-job-spec-factory.ts +++ b/src/model/cloud-runner/providers/k8s/kubernetes-job-spec-factory.ts @@ -20,6 +20,7 @@ class KubernetesJobSpecFactory { jobName: string, k8s: any, containerName: string, + ip: string = '', ) { const job = new k8s.V1Job(); job.apiVersion = 'batch/v1'; @@ -81,6 +82,7 @@ class KubernetesJobSpecFactory { return environmentVariable; }), + { name: 'LOG_SERVICE_IP', value: ip }, ], volumeMounts: [ { diff --git a/src/model/cloud-runner/providers/k8s/kubernetes-log-service.ts b/src/model/cloud-runner/providers/k8s/kubernetes-log-service.ts new file mode 100644 index 00000000..70d418fc --- /dev/null +++ b/src/model/cloud-runner/providers/k8s/kubernetes-log-service.ts @@ -0,0 +1,153 @@ +import { CoreV1Api } from '@kubernetes/client-node'; +import * as k8s from '@kubernetes/client-node'; +class KubernetesLogService { + // static async function, creates a deployment and service + static async createLogService(serviceAccountName: string, namespace: string, kubeClient: CoreV1Api) { + const serviceAccount = new k8s.V1ServiceAccount(); + serviceAccount.apiVersion = 'v1'; + serviceAccount.kind = 'ServiceAccount'; + serviceAccount.metadata = { + name: serviceAccountName, + }; + serviceAccount.automountServiceAccountToken = true; + + return kubeClient.createNamespacedServiceAccount(namespace, serviceAccount); + } + + static async deleteLogService(serviceAccountName: string, namespace: string, kubeClient: CoreV1Api) { + await kubeClient.deleteNamespacedServiceAccount(serviceAccountName, namespace); + } + + static async createLogDeployment(namespace: string, kubeClient: k8s.AppsV1Api, kubeClientCore: CoreV1Api) { + // json + /* + apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + service: http-fileserver + name: http-fileserver +spec: + replicas: 1 + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + service: http-fileserver + spec: + containers: + - image: my-docker-hub-account/http-fileserver-kubernetes:latest + imagePullPolicy: Always + name: http-fileserver + resources: {} + restartPolicy: Always +status: {} + */ + // create a deployment with above json + const deployment = new k8s.V1Deployment(); + deployment.apiVersion = 'apps/v1'; + deployment.kind = 'Deployment'; + deployment.metadata = { + name: 'http-fileserver', + labels: { + service: 'http-fileserver', + }, + }; + deployment.spec = { + selector: { + matchLabels: { + service: 'http-fileserver', + }, + }, + replicas: 1, + strategy: {}, + template: { + metadata: { + labels: { + service: 'http-fileserver', + }, + }, + spec: { + containers: [ + { + image: 'my-docker-hub-account/http-fileserver-kubernetes:latest', + imagePullPolicy: 'Always', + name: 'http-fileserver', + resources: {}, + }, + ], + restartPolicy: 'Always', + }, + }, + }; + await kubeClient.createNamespacedDeployment(namespace, deployment); + await this.createLogServiceExpose(namespace, kubeClientCore); + + // wait in loop until serivce ip address is exposed + + for (let index = 0; index < 10; index++) { + // wait for service to share ip address + await new Promise((resolve) => setTimeout(resolve, 10000)); + + // get ip address of service + const service = await kubeClientCore.readNamespacedService('http-fileserver', namespace); + const ip = service.body.status?.loadBalancer?.ingress?.[0]?.ip; + if (ip) { + return ip; + } + } + } + + // create kubernetes service to expose deployment + static async createLogServiceExpose(namespace: string, kubeClient: CoreV1Api) { + // json + /* + apiVersion: v1 + kind: Service + metadata: + creationTimestamp: null + labels: + service: http-fileserver + name: http-fileserver + spec: + ports: + - name: 80-80 + port: 80 + protocol: TCP + targetPort: 80 + selector: + service: http-fileserver + type: LoadBalancer + status: + loadBalancer: {} + */ + // create a service with above json + const service = new k8s.V1Service(); + service.apiVersion = 'v1'; + service.kind = 'Service'; + service.metadata = { + name: 'http-fileserver', + labels: { + service: 'http-fileserver', + }, + }; + service.spec = { + ports: [ + { + name: '80-80', + port: 80, + protocol: 'TCP', + targetPort: 80, + }, + ], + selector: { + service: 'http-fileserver', + }, + type: 'LoadBalancer', + }; + await kubeClient.createNamespacedService(namespace, service); + } +} +export default KubernetesLogService; diff --git a/src/model/cloud-runner/remote-client/remote-client-logger.ts b/src/model/cloud-runner/remote-client/remote-client-logger.ts index 0085f271..84c1b7f6 100644 --- a/src/model/cloud-runner/remote-client/remote-client-logger.ts +++ b/src/model/cloud-runner/remote-client/remote-client-logger.ts @@ -39,6 +39,7 @@ export class RemoteClientLogger { return; } CloudRunnerLogger.log(`Collected Logs`); + CloudRunnerLogger.log(process.env[`LOG_SERVICE_IP`] || ``); const logs = fs.readFileSync(RemoteClientLogger.LogFilePath).toString(); CloudRunnerLogger.log(logs); await Kubernetes.Instance.PushLogUpdate(logs);