From ad7c973b90bf82e1b896b27d37ebdb13aae28415 Mon Sep 17 00:00:00 2001 From: Jon Edvald Date: Sat, 22 Jun 2019 17:03:38 +0200 Subject: [PATCH] feat(container): allow configuring # of replicas for container services Also added `project-variables` project to showcase the usage. --- docs/reference/module-types/container.md | 16 +++++++-- .../reference/module-types/maven-container.md | 16 +++++++-- docs/using-garden/configuration-files.md | 5 ++- examples/demo-project/README.md | 3 ++ examples/project-variables/README.md | 10 ++++++ .../project-variables/backend/.dockerignore | 4 +++ examples/project-variables/backend/.gitignore | 27 ++++++++++++++ examples/project-variables/backend/Dockerfile | 14 ++++++++ examples/project-variables/backend/garden.yml | 14 ++++++++ .../backend/webserver/main.go | 17 +++++++++ .../project-variables/frontend/.dockerignore | 4 +++ .../project-variables/frontend/Dockerfile | 12 +++++++ examples/project-variables/frontend/app.js | 28 +++++++++++++++ .../project-variables/frontend/garden.yml | 28 +++++++++++++++ examples/project-variables/frontend/main.js | 3 ++ .../project-variables/frontend/package.json | 22 ++++++++++++ .../project-variables/frontend/test/integ.js | 17 +++++++++ examples/project-variables/garden.yml | 19 ++++++++++ .../src/plugins/container/config.ts | 18 ++++++++-- .../kubernetes/container/deployment.ts | 35 ++++++++++++++----- .../src/plugins/kubernetes/container/logs.ts | 11 ++++-- .../local/local-google-cloud-functions.ts | 1 + garden-service/test/integ/garden.yml | 2 ++ .../test/unit/src/plugins/container.ts | 6 ++++ .../plugins/kubernetes/container/ingress.ts | 1 + 25 files changed, 313 insertions(+), 20 deletions(-) create mode 100644 examples/demo-project/README.md create mode 100644 examples/project-variables/README.md create mode 100644 examples/project-variables/backend/.dockerignore create mode 100644 examples/project-variables/backend/.gitignore create mode 100644 examples/project-variables/backend/Dockerfile create mode 100644 examples/project-variables/backend/garden.yml create mode 100644 examples/project-variables/backend/webserver/main.go create mode 100644 examples/project-variables/frontend/.dockerignore create mode 100644 examples/project-variables/frontend/Dockerfile create mode 100644 examples/project-variables/frontend/app.js create mode 100644 examples/project-variables/frontend/garden.yml create mode 100644 examples/project-variables/frontend/main.js create mode 100644 examples/project-variables/frontend/package.json create mode 100644 examples/project-variables/frontend/test/integ.js create mode 100644 examples/project-variables/garden.yml diff --git a/docs/reference/module-types/container.md b/docs/reference/module-types/container.md index 305fc39d94..19328d84de 100644 --- a/docs/reference/module-types/container.md +++ b/docs/reference/module-types/container.md @@ -303,7 +303,7 @@ The names of any services that this service depends on at runtime, and the names [services](#services) > annotations -Annotations to attach to the service (Note: May not be applicable to all providers) +Annotations to attach to the service (Note: May not be applicable to all providers). | Type | Required | | ---- | -------- | @@ -351,7 +351,7 @@ services: [services](#services) > daemon -Whether to run the service as a daemon (to ensure only one runs per node). +Whether to run the service as a daemon (to ensure exactly one instance runs per node). May not be supported by all providers. | Type | Required | | ---- | -------- | @@ -652,6 +652,17 @@ Set this to expose the service on the specified port on the host node (may not b | ---- | -------- | | `number` | No +### `services[].replicas` + +[services](#services) > replicas + +The number of instances of the service to deploy. +Note: This setting may be overridden or ignored in some cases. For example, when running with `daemon: true`, with hot-reloading enabled, or if the provider doesn't support multiple replicas. + +| Type | Required | +| ---- | -------- | +| `number` | No + ### `services[].volumes[]` [services](#services) > volumes @@ -929,6 +940,7 @@ services: servicePort: hostPort: nodePort: + replicas: 1 volumes: - name: containerPath: diff --git a/docs/reference/module-types/maven-container.md b/docs/reference/module-types/maven-container.md index 2eecf59ad0..f275078e41 100644 --- a/docs/reference/module-types/maven-container.md +++ b/docs/reference/module-types/maven-container.md @@ -308,7 +308,7 @@ The names of any services that this service depends on at runtime, and the names [services](#services) > annotations -Annotations to attach to the service (Note: May not be applicable to all providers) +Annotations to attach to the service (Note: May not be applicable to all providers). | Type | Required | | ---- | -------- | @@ -356,7 +356,7 @@ services: [services](#services) > daemon -Whether to run the service as a daemon (to ensure only one runs per node). +Whether to run the service as a daemon (to ensure exactly one instance runs per node). May not be supported by all providers. | Type | Required | | ---- | -------- | @@ -657,6 +657,17 @@ Set this to expose the service on the specified port on the host node (may not b | ---- | -------- | | `number` | No +### `services[].replicas` + +[services](#services) > replicas + +The number of instances of the service to deploy. +Note: This setting may be overridden or ignored in some cases. For example, when running with `daemon: true`, with hot-reloading enabled, or if the provider doesn't support multiple replicas. + +| Type | Required | +| ---- | -------- | +| `number` | No + ### `services[].volumes[]` [services](#services) > volumes @@ -964,6 +975,7 @@ services: servicePort: hostPort: nodePort: + replicas: 1 volumes: - name: containerPath: diff --git a/docs/using-garden/configuration-files.md b/docs/using-garden/configuration-files.md index 8ea611a6cb..5c342d8c90 100644 --- a/docs/using-garden/configuration-files.md +++ b/docs/using-garden/configuration-files.md @@ -399,15 +399,14 @@ key: kind: Project ... variables: - global-memory-limit: 100 + default-replicas: 3 --- kind: Module ... services: - name: my-service ... - limits: - memory: ${var.global-memory-limit} # <- resolves to a number, as opposed to the string "100" + replicas: ${var.default-replicas} # <- resolves to a number, as opposed to the string "3" ``` If, however, the template string is not the whole string being interpolated, but a component of it, the value is diff --git a/examples/demo-project/README.md b/examples/demo-project/README.md new file mode 100644 index 0000000000..4e97c83d4d --- /dev/null +++ b/examples/demo-project/README.md @@ -0,0 +1,3 @@ +# Demo project + +A very basic demo project for Garden. Used in the [Quick Start guide](https://docs.garden.io/basics/quick-start). diff --git a/examples/project-variables/README.md b/examples/project-variables/README.md new file mode 100644 index 0000000000..b66d27b300 --- /dev/null +++ b/examples/project-variables/README.md @@ -0,0 +1,10 @@ +# Project variables + +This variant of the basic [demo project](../demo-project/README.md) demonstrate the use of project variables. + +In this example, we set a project variable in the [project config](./garden.yml) called `service-replicas` and +reference that variable in the module configs, in this case to set the number of replicas per service. + +We also show how you can alternate these variables by the environment you're running, by overriding the default value +in the `local` environment. In this case, we only want _one_ replica of each service while developing locally, but +default to three when deploying remotely. diff --git a/examples/project-variables/backend/.dockerignore b/examples/project-variables/backend/.dockerignore new file mode 100644 index 0000000000..1cd4736667 --- /dev/null +++ b/examples/project-variables/backend/.dockerignore @@ -0,0 +1,4 @@ +node_modules +Dockerfile +garden.yml +app.yaml diff --git a/examples/project-variables/backend/.gitignore b/examples/project-variables/backend/.gitignore new file mode 100644 index 0000000000..eb086d61c3 --- /dev/null +++ b/examples/project-variables/backend/.gitignore @@ -0,0 +1,27 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +.vscode/settings.json +webserver/*server* diff --git a/examples/project-variables/backend/Dockerfile b/examples/project-variables/backend/Dockerfile new file mode 100644 index 0000000000..eca3125f6e --- /dev/null +++ b/examples/project-variables/backend/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.8.3-alpine +MAINTAINER Aurelien PERRIER + +ENV webserver_path /go/src/github.com/perriea/webserver/ +ENV PATH $PATH:$webserver_path + +WORKDIR $webserver_path +COPY webserver/ . + +RUN go build . + +ENTRYPOINT ./webserver + +EXPOSE 8080 diff --git a/examples/project-variables/backend/garden.yml b/examples/project-variables/backend/garden.yml new file mode 100644 index 0000000000..ca6fd8ee64 --- /dev/null +++ b/examples/project-variables/backend/garden.yml @@ -0,0 +1,14 @@ +kind: Module +name: backend +description: Backend service container +type: container +services: + - name: backend + replicas: ${var.service-replicas} # <- Refers to the variable set in the project config + ports: + - name: http + containerPort: 8080 + servicePort: 80 + ingresses: + - path: /hello-backend + port: http diff --git a/examples/project-variables/backend/webserver/main.go b/examples/project-variables/backend/webserver/main.go new file mode 100644 index 0000000000..34ae7d9838 --- /dev/null +++ b/examples/project-variables/backend/webserver/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + "net/http" +) + +func handler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Hello from Go!") +} + +func main() { + http.HandleFunc("/hello-backend", handler) + fmt.Println("Server running...") + + http.ListenAndServe(":8080", nil) +} diff --git a/examples/project-variables/frontend/.dockerignore b/examples/project-variables/frontend/.dockerignore new file mode 100644 index 0000000000..1cd4736667 --- /dev/null +++ b/examples/project-variables/frontend/.dockerignore @@ -0,0 +1,4 @@ +node_modules +Dockerfile +garden.yml +app.yaml diff --git a/examples/project-variables/frontend/Dockerfile b/examples/project-variables/frontend/Dockerfile new file mode 100644 index 0000000000..4a418d7d1e --- /dev/null +++ b/examples/project-variables/frontend/Dockerfile @@ -0,0 +1,12 @@ +FROM node:9-alpine + +ENV PORT=8080 +EXPOSE ${PORT} +WORKDIR /app + +ADD package.json /app +RUN npm install + +ADD . /app + +CMD ["npm", "start"] diff --git a/examples/project-variables/frontend/app.js b/examples/project-variables/frontend/app.js new file mode 100644 index 0000000000..f04bbf5a3b --- /dev/null +++ b/examples/project-variables/frontend/app.js @@ -0,0 +1,28 @@ +const express = require('express'); +const request = require('request-promise') +const app = express(); + +const backendServiceEndpoint = `http://backend/hello-backend` +// const backendServiceEndpoint = `http://demo-project.local.app.garden/hello-backend` + +app.get('/hello-frontend', (req, res) => res.send('Hello from the frontend!')); + +app.get('/call-backend', (req, res) => { + // Query the backend and return the response + request.get(backendServiceEndpoint) + .then(message => { + message = `Backend says: '${message}'` + res.json({ + message, + }) + }) + .catch(err => { + res.statusCode = 500 + res.json({ + error: err, + message: "Unable to reach service at " + backendServiceEndpoint, + }) + }); +}); + +module.exports = { app } diff --git a/examples/project-variables/frontend/garden.yml b/examples/project-variables/frontend/garden.yml new file mode 100644 index 0000000000..3326a989f1 --- /dev/null +++ b/examples/project-variables/frontend/garden.yml @@ -0,0 +1,28 @@ +kind: Module +name: frontend +description: Frontend service container +type: container +services: + - name: frontend + replicas: ${var.service-replicas} # <- Refers to the variable set in the project config + ports: + - name: http + containerPort: 8080 + healthCheck: + httpGet: + path: /hello-frontend + port: http + ingresses: + - path: /hello-frontend + port: http + - path: /call-backend + port: http + dependencies: + - backend +tests: + - name: unit + args: [npm, test] + - name: integ + args: [npm, run, integ] + dependencies: + - backend diff --git a/examples/project-variables/frontend/main.js b/examples/project-variables/frontend/main.js new file mode 100644 index 0000000000..ab66491126 --- /dev/null +++ b/examples/project-variables/frontend/main.js @@ -0,0 +1,3 @@ +const { app } = require('./app'); + +app.listen(process.env.PORT, '0.0.0.0', () => console.log('Frontend service started')); diff --git a/examples/project-variables/frontend/package.json b/examples/project-variables/frontend/package.json new file mode 100644 index 0000000000..e3da030191 --- /dev/null +++ b/examples/project-variables/frontend/package.json @@ -0,0 +1,22 @@ +{ + "name": "frontend", + "version": "1.0.0", + "description": "Simple Node.js docker service", + "main": "main.js", + "scripts": { + "start": "node main.js", + "test": "echo OK", + "integ": "node_modules/mocha/bin/mocha test/integ.js" + }, + "author": "garden.io ", + "license": "ISC", + "dependencies": { + "express": "^4.16.2", + "request": "^2.83.0", + "request-promise": "^4.2.2" + }, + "devDependencies": { + "mocha": "^5.1.1", + "supertest": "^3.0.0" + } +} diff --git a/examples/project-variables/frontend/test/integ.js b/examples/project-variables/frontend/test/integ.js new file mode 100644 index 0000000000..ea1ccd85ef --- /dev/null +++ b/examples/project-variables/frontend/test/integ.js @@ -0,0 +1,17 @@ +const supertest = require("supertest") +const { app } = require("../app") + +describe('GET /call-backend', () => { + const agent = supertest.agent(app) + + it('should respond with a message from the backend service', (done) => { + agent + .get("/call-backend") + .expect(200, { message: "Backend says: 'Hello from Go!'" }) + .end((err) => { + if (err) return done(err) + done() + }) + }) +}) + diff --git a/examples/project-variables/garden.yml b/examples/project-variables/garden.yml new file mode 100644 index 0000000000..b96813597b --- /dev/null +++ b/examples/project-variables/garden.yml @@ -0,0 +1,19 @@ +kind: Project +name: project-variables +variables: + # This variable is referenced in the module configs, and overridden in the local project below + service-replicas: 3 +environments: + - name: local + providers: + - name: local-kubernetes + variables: + # We only want one replica of each service when developing locally + service-replicas: 1 + - name: testing + providers: + - name: kubernetes + context: gke_garden-dev-200012_europe-west1-b_garden-dev-1 + namespace: ${project.name}-testing-${local.env.CIRCLE_BUILD_NUM || "default"} + defaultHostname: ${project.name}-testing.dev-1.sys.garden + buildMode: cluster-docker diff --git a/garden-service/src/plugins/container/config.ts b/garden-service/src/plugins/container/config.ts index 2fdab1551b..b69eb87c63 100644 --- a/garden-service/src/plugins/container/config.ts +++ b/garden-service/src/plugins/container/config.ts @@ -87,6 +87,7 @@ export interface ContainerServiceSpec extends CommonServiceSpec { hotReloadArgs?: string[], limits: ServiceLimitSpec, ports: ServicePortSpec[], + replicas: number, volumes: ServiceVolumeSpec[], } @@ -224,7 +225,7 @@ const volumeSchema = Joi.object() const serviceSchema = baseServiceSpecSchema .keys({ annotations: annotationsSchema - .description("Annotations to attach to the service (Note: May not be applicable to all providers)"), + .description("Annotations to attach to the service (Note: May not be applicable to all providers)."), command: Joi.array().items(Joi.string()) .description("The command/entrypoint to run the container with when starting the service.") .example([commandExample, {}]), @@ -233,7 +234,10 @@ const serviceSchema = baseServiceSpecSchema .example([["npm", "start"], {}]), daemon: Joi.boolean() .default(false) - .description("Whether to run the service as a daemon (to ensure only one runs per node)."), + .description(deline` + Whether to run the service as a daemon (to ensure exactly one instance runs per node). + May not be supported by all providers. + `), ingresses: joiArray(ingressSchema) .description("List of ingress endpoints that the service exposes.") .example([ @@ -259,6 +263,16 @@ const serviceSchema = baseServiceSpecSchema ports: joiArray(portSchema) .unique("name") .description("List of ports that the service container exposes."), + replicas: Joi.number() + .integer() + .min(1) + .default(1) + .description(deline` + The number of instances of the service to deploy. + + Note: This setting may be overridden or ignored in some cases. For example, when running with \`daemon: true\`, + with hot-reloading enabled, or if the provider doesn't support multiple replicas. + `), volumes: joiArray(volumeSchema) .unique("name") .description("List of volumes that should be mounted when deploying the container."), diff --git a/garden-service/src/plugins/kubernetes/container/deployment.ts b/garden-service/src/plugins/kubernetes/container/deployment.ts index 4febf887fa..27b2474296 100644 --- a/garden-service/src/plugins/kubernetes/container/deployment.ts +++ b/garden-service/src/plugins/kubernetes/container/deployment.ts @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { V1Container } from "@kubernetes/client-node" import { extend, keyBy, set, toPairs } from "lodash" import { RuntimeContext, Service, ServiceStatus } from "../../../types/service" import { ContainerModule, ContainerService } from "../../container/config" @@ -27,6 +28,7 @@ import { DeployServiceParams } from "../../../types/plugin/service/deployService import { DeleteServiceParams } from "../../../types/plugin/service/deleteService" import { millicpuToString, kilobytesToString } from "../util" import { gardenAnnotationKey } from "../../../util/string" +import chalk from "chalk" export const DEFAULT_CPU_REQUEST = "10m" export const DEFAULT_MEMORY_REQUEST = "64Mi" @@ -68,7 +70,7 @@ export async function createContainerObjects( const namespace = await getAppNamespace(k8sCtx, log, provider) const api = await KubeApi.factory(log, provider.config.context) const ingresses = await createIngressResources(api, provider, namespace, service) - const deployment = await createDeployment(provider, service, runtimeContext, namespace, enableHotReload) + const deployment = await createDeployment({ provider, service, runtimeContext, namespace, enableHotReload, log }) const kubeservices = await createServiceResources(service, namespace) const objects = [deployment, ...kubeservices, ...ingresses] @@ -82,17 +84,32 @@ export async function createContainerObjects( }) } +interface CreateDeploymentParams { + provider: KubernetesProvider, + service: ContainerService, + runtimeContext: RuntimeContext, + namespace: string, + enableHotReload: boolean, + log: LogEntry, +} + export async function createDeployment( - provider: KubernetesProvider, service: ContainerService, - runtimeContext: RuntimeContext, namespace: string, enableHotReload: boolean, + { provider, service, runtimeContext, namespace, enableHotReload, log }: CreateDeploymentParams, ): Promise { const spec = service.spec - // TODO: support specifying replica count - const configuredReplicas = 1 // service.spec.count[env.name] || 1 + let configuredReplicas = service.spec.replicas const deployment: any = deploymentConfig(service, configuredReplicas, namespace) const envVars = { ...runtimeContext.envVars, ...service.spec.env } + if (enableHotReload && service.spec.replicas > 1) { + log.warn({ + msg: chalk.yellow(`Ignoring replicas config on container service ${service.name} while in hot-reload mode`), + symbol: "warning", + }) + configuredReplicas = 1 + } + const env: KubeEnvVar[] = toPairs(envVars).map(([name, value]) => ({ name, value: value + "" })) // expose some metadata to the container @@ -114,7 +131,7 @@ export async function createDeployment( const registryConfig = provider.config.deploymentRegistry const imageId = await containerHelpers.getDeploymentImageId(service.module, registryConfig) - const container: any = { + const container: V1Container = { name: service.name, image: imageId, env, @@ -162,7 +179,7 @@ export async function createDeployment( const ports = spec.ports for (const port of ports) { - container.ports.push({ + container.ports!.push({ name: port.name, protocol: port.protocol, containerPort: port.containerPort, @@ -180,7 +197,7 @@ export async function createDeployment( // For daemons we can expose host ports directly on the Pod, as opposed to only via the Service resource. // This allows us to choose any port. // TODO: validate that conflicting ports are not defined. - container.ports.push({ + container.ports!.push({ protocol: port.protocol, containerPort: port.containerPort, hostPort: port.hostPort, @@ -207,7 +224,7 @@ export async function createDeployment( } // this is important for status checks to work correctly, because how K8s normalizes resources - if (!container.ports.length) { + if (!container.ports!.length) { delete container.ports } diff --git a/garden-service/src/plugins/kubernetes/container/logs.ts b/garden-service/src/plugins/kubernetes/container/logs.ts index cd522b6d74..e64b3db5f2 100644 --- a/garden-service/src/plugins/kubernetes/container/logs.ts +++ b/garden-service/src/plugins/kubernetes/container/logs.ts @@ -14,12 +14,19 @@ import { KubernetesPluginContext } from "../config" import { createDeployment } from "./deployment" export async function getServiceLogs(params: GetServiceLogsParams) { - const { ctx, log, service } = params + const { ctx, log, service, runtimeContext } = params const k8sCtx = ctx const context = k8sCtx.provider.config.context const namespace = await getAppNamespace(k8sCtx, log, k8sCtx.provider) - const resources = [await createDeployment(k8sCtx.provider, service, params.runtimeContext, namespace, false)] + const resources = [await createDeployment({ + provider: k8sCtx.provider, + service, + runtimeContext, + namespace, + enableHotReload: false, + log, + })] return getAllLogs({ ...params, context, namespace, resources }) } diff --git a/garden-service/src/plugins/local/local-google-cloud-functions.ts b/garden-service/src/plugins/local/local-google-cloud-functions.ts index 640ae9a785..0c29b573ad 100644 --- a/garden-service/src/plugins/local/local-google-cloud-functions.ts +++ b/garden-service/src/plugins/local/local-google-cloud-functions.ts @@ -95,6 +95,7 @@ export const gardenPlugin = (): GardenPlugin => ({ servicePort: emulatorPort, }, ], + replicas: 1, volumes: [], } diff --git a/garden-service/test/integ/garden.yml b/garden-service/test/integ/garden.yml index 573b1eb54b..4d02beab79 100644 --- a/garden-service/test/integ/garden.yml +++ b/garden-service/test/integ/garden.yml @@ -21,6 +21,8 @@ tests: command: [npm, run, integ-full, --, --only=tasks, --showlog=true, --env=testing] - name: hot-reload # Tests for hot-reload are currently being skipped command: [npm, run, integ-full, --, --only=hot-reload, --showlog=true, --env=testing] + - name: project-variables + command: [npm, run, integ-full, --, --only=project-variables, --showlog=true, --env=testing] - name: vote-helm command: [npm, run, integ-full, --, --only=vote-helm, --showlog=true, --env=testing] - name: vote diff --git a/garden-service/test/unit/src/plugins/container.ts b/garden-service/test/unit/src/plugins/container.ts index 94849f329e..3425740b86 100644 --- a/garden-service/test/unit/src/plugins/container.ts +++ b/garden-service/test/unit/src/plugins/container.ts @@ -419,6 +419,7 @@ describe("plugins.container", () => { containerPort: 8080, servicePort: 8080, }], + replicas: 1, volumes: [], }], tasks: [{ @@ -485,6 +486,7 @@ describe("plugins.container", () => { memory: 456, }, ports: [{ name: "http", protocol: "TCP", containerPort: 8080, servicePort: 8080 }], + replicas: 1, volumes: [], }], tasks: @@ -535,6 +537,7 @@ describe("plugins.container", () => { memory: 456, }, ports: [{ name: "http", protocol: "TCP", containerPort: 8080, servicePort: 8080 }], + replicas: 1, volumes: [], }, }], @@ -606,6 +609,7 @@ describe("plugins.container", () => { limits: defaultContainerLimits, env: {}, ports: [], + replicas: 1, volumes: [], }], tasks: [{ @@ -666,6 +670,7 @@ describe("plugins.container", () => { }, limits: defaultContainerLimits, ports: [], + replicas: 1, volumes: [], }], tasks: [{ @@ -717,6 +722,7 @@ describe("plugins.container", () => { }, limits: defaultContainerLimits, ports: [], + replicas: 1, volumes: [], }], tasks: [{ diff --git a/garden-service/test/unit/src/plugins/kubernetes/container/ingress.ts b/garden-service/test/unit/src/plugins/kubernetes/container/ingress.ts index bdec3c47a9..f185d42edd 100644 --- a/garden-service/test/unit/src/plugins/kubernetes/container/ingress.ts +++ b/garden-service/test/unit/src/plugins/kubernetes/container/ingress.ts @@ -345,6 +345,7 @@ describe("createIngressResources", () => { ingresses, limits: defaultContainerLimits, ports, + replicas: 1, volumes: [], } const moduleConfig = {