Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add k8s-env --dev option #3667

Merged
merged 17 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# NODE_VERSION is set by default in the config.ts, the following value will only
# be used if you build images by default with docker build
ARG NODE_VERSION=18.19.1
FROM terascope/node-base:${NODE_VERSION}

ENV NODE_ENV production

ENV YARN_SETUP_ARGS "--prod=false --silent --frozen-lockfile"

# Check to see if distutils is installed because python 3.12 removed it
RUN python3 -c "import distutils" || (apk update && apk add py3-setuptools)

EXPOSE 5678

# set up the volumes
VOLUME /app/config /app/logs /app/assets
ENV TERAFOUNDATION_CONFIG /app/config/teraslice.yaml

CMD ["yarn", "start:nodemon"]
45 changes: 45 additions & 0 deletions e2e/k8s/kindConfigDefaultPortsDev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#
# This config file is used in the case that "--dev" is enabled in k8s-env
# which will mount the host machines teraslice repo to the k8s resources
#
kind: Cluster
name: k8s-env
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30200 # Map internal elasticsearch service to host port
hostPort: 9200
- containerPort: 30678 # Map internal teraslice service to host port
hostPort: 5678
- containerPort: 30092 # Map internal kafka service to host port
hostPort: 9092
- containerPort: 30900 # Map internal minio service to host port
hostPort: 9000
extraMounts:
- hostPath: ./e2e/autoload
containerPath: /autoload
### All extra mounts that the Dockerfile.dev image needs to run teraslice
- hostPath: ./packages
containerPath: /packages
- hostPath: ./scripts
containerPath: /scripts
- hostPath: ./types
containerPath: /types
- hostPath: ./.yarn
containerPath: /.yarn
- hostPath: ./.yarnclean.ci
containerPath: /.yarnclean.ci
- hostPath: ./package.json
containerPath: /package.json
- hostPath: ./yarn.lock
containerPath: /yarn.lock
- hostPath: ./tsconfig.json
containerPath: /tsconfig.json
- hostPath: ./.yarnrc
containerPath: /.yarnrc
- hostPath: ./service.js
containerPath: /service.js
- hostPath: ./node_modules
containerPath: /node_modules
###
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"setup": "yarn $YARN_SETUP_ARGS && yarn run build --force",
"start": "node service.js",
"start:dev": "yarn build && env NODE_ENV=development node service.js",
"start:nodemon": "nodemon --exitcrash service.js",
"sync": "ts-scripts sync",
"test": "ts-scripts test"
},
Expand All @@ -57,6 +58,7 @@
"jest-extended": "^3.2.4",
"jest-watch-typeahead": "^2.2.2",
"node-notifier": "^10.0.1",
"nodemon": "^3.1.4",
"ts-jest": "^29.1.4",
"typescript": "~5.2.2"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/scripts/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@terascope/scripts",
"displayName": "Scripts",
"version": "0.78.0",
"version": "0.79.0",
"description": "A collection of terascope monorepo scripts",
"homepage": "https://github.com/terascope/teraslice/tree/master/packages/scripts#readme",
"bugs": {
Expand Down
9 changes: 8 additions & 1 deletion packages/scripts/src/cmds/k8s-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const cmd: CommandModule = {
builder(yargs) {
return yargs
.example('TEST_ELASTICSEARCH=\'true\' ELASTICSEARCH_PORT=\'9200\' $0 k8s-env', 'Start a kind kubernetes cluster running teraslice from your local repository and elasticsearch.')
.example('TEST_ELASTICSEARCH=\'true\' ELASTICSEARCH_PORT=\'9200\' $0 k8s-env --dev', 'Start a kind kubernetes cluster running teraslice in dev mode. Faster build times.')
.example('TEST_ELASTICSEARCH=\'true\' ELASTICSEARCH_PORT=\'9200\' $0 k8s-env --teraslice-image=terascope/teraslice:v0.91.0-nodev18.18.2', 'Start a kind kubernetes cluster running teraslice from a specific docker image and elasticsearch.')
.example('TEST_ELASTICSEARCH=\'true\' ELASTICSEARCH_PORT=\'9200\' TEST_KAFKA=\'true\' KAFKA_PORT=\'9092\' $0 k8s-env', 'Start a kind kubernetes cluster running teraslice, elasticsearch, kafka, and zookeeper.')
.example('TEST_ELASTICSEARCH=\'true\' ELASTICSEARCH_PORT=\'9200\' $0 k8s-env --skip-build', 'Start a kind kubernetes cluster, but skip building a new teraslice docker image.')
Expand Down Expand Up @@ -93,6 +94,11 @@ const cmd: CommandModule = {
default: 'elasticsearch-next',
choices: ['elasticsearch-next', 's3'],
})
.option('dev', {
description: 'Mounts local teraslice to k8s resources for faster development.',
type: 'boolean',
default: false
})
.check((args) => {
if (args['asset-storage'] === 's3' && process.env.TEST_MINIO !== 'true') {
throw new Error('You chose "s3" as an asset storage but don\'t have the minio service enabled.\n'
Expand All @@ -118,7 +124,8 @@ const cmd: CommandModule = {
kindClusterName: argv['cluster-name'] as string,
k8sVersion: argv['k8s-version'] as string,
terasliceImage: argv['teraslice-image'] as string,
assetStorage: argv['asset-storage'] as string
assetStorage: argv['asset-storage'] as string,
dev: Boolean(argv.dev)
};

if (Boolean(argv.rebuild) === true) {
Expand Down
7 changes: 7 additions & 0 deletions packages/scripts/src/helpers/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EmptyObject } from '@terascope/utils';
import type { V1Volume, V1VolumeMount } from '@kubernetes/client-node';

export type PackageInfo = {
dir: string;
Expand Down Expand Up @@ -157,3 +158,9 @@ export type KindCluster = {
}
]
}

export interface TsVolumeSet {
extraMounts: any[],
volumes: V1Volume[],
volumeMounts: V1VolumeMount[]
}
24 changes: 21 additions & 3 deletions packages/scripts/src/helpers/k8s-env/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import execa from 'execa';
import {
dockerTag,
isKindInstalled,
Expand All @@ -20,7 +21,6 @@ const e2eImage = `${rootInfo.name}:e2e-nodev${config.NODE_VERSION}`;

export async function launchK8sEnv(options: K8sEnvOptions) {
signale.pending('Starting k8s environment with the following options: ', options);

const kind = new Kind(options.k8sVersion, options.kindClusterName);
// TODO: create a kind class
const kindInstalled = await isKindInstalled();
Expand All @@ -36,9 +36,26 @@ export async function launchK8sEnv(options: K8sEnvOptions) {
process.exit(1);
}

// If --dev is true, we must run yarn setup before creating resources
// We need a local node_modules folder built to add it as a volume
if (options.dev) {
if (process.version.substring(1) !== options.nodeVersion) {
throw new Error(`The node version this process is running on (${process.version}) does not match
the --node-version set in k8s-env (v${options.nodeVersion}). Check your version by running "node -v"`);
}
signale.info(`Running yarn setup with node ${process.version}...`);
try {
execa.commandSync('yarn setup');
} catch (err) {
signale.fatal(err);
await kind.destroyCluster();
process.exit(1);
}
}

signale.pending('Creating kind cluster');
try {
await kind.createCluster(options.tsPort);
await kind.createCluster(options.tsPort, options.dev);
} catch (err) {
signale.error(err);
// Do not destroy existing cluster if that was the cause of failure
Expand Down Expand Up @@ -144,7 +161,8 @@ async function buildAndTagTerasliceImage(options:K8sEnvOptions) {
dryRun: true,
nodeSuffix: true,
nodeVersion: options.nodeVersion,
type: PublishType.Dev
type: PublishType.Dev,
useDevFile: options.dev
};
runImage = await buildDevDockerImage(publishOptions);
} catch (err) {
Expand Down
1 change: 1 addition & 0 deletions packages/scripts/src/helpers/k8s-env/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface K8sEnvOptions {
assetStorage: string;
terasliceImage?: string;
resetStore?: boolean;
dev: boolean;
}

// TODO: create a common parent for each resource type,
Expand Down
37 changes: 35 additions & 2 deletions packages/scripts/src/helpers/k8s-env/k8s.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { getE2eK8sDir } from '../../helpers/packages';
import { K8sEnvOptions } from './interfaces';
import signale from '../signale';
import * as config from '../config';
import { getVolumesFromDockerfile } from '../kind';

const logger = debugLogger('ts-scripts:k8s-env');
export class K8s {
Expand Down Expand Up @@ -58,6 +59,35 @@ export class K8s {
}
}

mountLocalTeraslice(masterDeployment: k8sClient.V1Deployment) {
const dockerfileMounts = getVolumesFromDockerfile(true, logger);
if (masterDeployment.spec?.template.spec?.containers[0].volumeMounts) {
masterDeployment.spec.template.spec.containers[0].volumeMounts
.push(...dockerfileMounts.volumeMounts);
}
if (masterDeployment.spec?.template.spec?.volumes) {
masterDeployment.spec.template.spec.volumes
.push(...dockerfileMounts.volumes);
}

/// Pass in env so master passes volumes to ex's and workers
if (masterDeployment.spec?.template.spec?.containers[0].env) {
masterDeployment.spec.template.spec.containers[0].env.push(
{
name: 'MOUNT_LOCAL_TERASLICE',
value: JSON.stringify(dockerfileMounts)
}
);
} else if (masterDeployment.spec?.template.spec?.containers[0]) {
masterDeployment.spec.template.spec.containers[0].env = [
{
name: 'MOUNT_LOCAL_TERASLICE',
value: JSON.stringify(dockerfileMounts)
}
];
}
}

async deployK8sTeraslice(wait = false, options: K8sEnvOptions | undefined = undefined) {
signale.pending('Begin teraslice deployment...');
const e2eK8sDir = getE2eK8sDir();
Expand All @@ -76,7 +106,7 @@ export class K8s {
masterTerafoundation.teraslice.kubernetes_image = `teraslice-workspace:e2e-nodev${config.NODE_VERSION}`;
if (options) {
const storageType = options.assetStorage;
masterTerafoundation.terafoundation.asset_storage_connection_type = storageType;
masterTerafoundation.teraslice.asset_storage_connection_type = storageType;
}
masterConfigMap.data = { 'teraslice.yaml': k8sClient.dumpYaml(masterTerafoundation) };
masterConfigMap.metadata = { name: 'teraslice-master' };
Expand All @@ -94,7 +124,7 @@ export class K8s {
workerTerafoundation.teraslice.kubernetes_image = `teraslice-workspace:e2e-nodev${config.NODE_VERSION}`;
if (options) {
const storageType = options.assetStorage;
workerTerafoundation.terafoundation.asset_storage_connection_type = storageType;
workerTerafoundation.teraslice.asset_storage_connection_type = storageType;
}
workerConfigMap.data = { 'teraslice.yaml': k8sClient.dumpYaml(workerTerafoundation) };
workerConfigMap.metadata = { name: 'teraslice-worker' };
Expand All @@ -114,6 +144,9 @@ export class K8s {
if (yamlTSMasterDeployment.spec?.template.spec?.containers[0]) {
yamlTSMasterDeployment.spec.template.spec.containers[0].image = `teraslice-workspace:e2e-nodev${config.NODE_VERSION}`;
}
if (options?.dev) {
this.mountLocalTeraslice(yamlTSMasterDeployment);
}
const response = await this.k8sAppsV1Api.createNamespacedDeployment('ts-dev1', yamlTSMasterDeployment);
logger.debug('deployK8sTeraslice yamlTSMasterDeployment: ', response.body);
} catch (err) {
Expand Down
Loading
Loading