diff --git a/core/src/plugins/kubernetes/commands/pull-image.ts b/core/src/plugins/kubernetes/commands/pull-image.ts index bde88eb6d6..f242cb3f04 100644 --- a/core/src/plugins/kubernetes/commands/pull-image.ts +++ b/core/src/plugins/kubernetes/commands/pull-image.ts @@ -15,7 +15,7 @@ import { KubeApi } from "../api.js" import type { Log } from "../../../logger/log-entry.js" import { containerHelpers } from "../../container/helpers.js" import { PodRunner } from "../run.js" -import { dockerAuthSecretKey, getK8sUtilImageName, systemDockerAuthSecretName } from "../constants.js" +import { dockerAuthSecretKey, getK8sUtilImagePath, systemDockerAuthSecretName } from "../constants.js" import { getAppNamespace, getSystemNamespace } from "../namespace.js" import { randomString } from "../../../util/string.js" import type { PluginContext } from "../../../plugin-context.js" @@ -155,7 +155,7 @@ export async function pullBuild(params: PullParams) { containers: [ { name: "main", - image: getK8sUtilImageName(), + image: getK8sUtilImagePath(ctx.provider.config.utilImageRegistryDomain), command: ["sleep", "" + (imagePullTimeoutSeconds + 10)], volumeMounts: [ { diff --git a/core/src/plugins/kubernetes/config.ts b/core/src/plugins/kubernetes/config.ts index 4e65a16b62..b6c94d727c 100644 --- a/core/src/plugins/kubernetes/config.ts +++ b/core/src/plugins/kubernetes/config.ts @@ -40,7 +40,7 @@ import type { SyncDefaults } from "./sync.js" import { syncDefaultsSchema } from "./sync.js" import { KUBECTL_DEFAULT_TIMEOUT } from "./kubectl.js" import { DOCS_BASE_URL } from "../../constants.js" -import { defaultKanikoImageName, defaultSystemNamespace } from "./constants.js" +import { defaultKanikoImageName, defaultUtilImageRegistryDomain, defaultSystemNamespace } from "./constants.js" import type { LocalKubernetesClusterType } from "./local/config.js" import type { EphemeralKubernetesClusterType } from "./ephemeral/config.js" @@ -125,6 +125,7 @@ export interface ClusterBuildkitCacheConfig { export type KubernetesClusterType = LocalKubernetesClusterType | EphemeralKubernetesClusterType export interface KubernetesConfig extends BaseProviderConfig { + utilImageRegistryDomain: string buildMode: ContainerBuildMode clusterBuildkit?: { cache: ClusterBuildkitCacheConfig[] @@ -407,9 +408,20 @@ const buildkitCacheConfigurationSchema = () => ), }) +export const utilImageRegistryDomainSpec = joi.string().default(defaultUtilImageRegistryDomain).description(dedent` + The container registry domain that should be used for pulling Garden utility images (such as the + image used in the Kubernetes sync utility Pod). + + If you have your own Docker Hub registry mirror, you can set the domain here and the utility images + will be pulled from there. This can be useful to e.g. avoid Docker Hub rate limiting. + + Otherwise the utility images are pulled directly from Docker Hub by default. + `) + export const kubernetesConfigBase = () => providerConfigBaseSchema() .keys({ + utilImageRegistryDomain: utilImageRegistryDomainSpec, buildMode: joi .string() .valid("local-docker", "kaniko", "cluster-buildkit") diff --git a/core/src/plugins/kubernetes/constants.ts b/core/src/plugins/kubernetes/constants.ts index c017e369a9..1d9e37bb4c 100644 --- a/core/src/plugins/kubernetes/constants.ts +++ b/core/src/plugins/kubernetes/constants.ts @@ -10,9 +10,6 @@ import type { DockerImageWithDigest } from "../../util/string.js" import { gardenEnv } from "../../constants.js" import { makeDocsLinkPlain } from "../../docs/common.js" -export const rsyncPortName = "garden-rsync" -export const buildSyncVolumeName = `garden-sync` - export const MAX_CONFIGMAP_DATA_SIZE = 1024 * 1024 // max ConfigMap data size is 1MB // max ConfigMap data size is 1MB but we need to factor in overhead, plus in some cases the log is duplicated in // the outputs field, so we cap at 250kB. @@ -24,50 +21,85 @@ export const PROXY_CONTAINER_SSH_TUNNEL_PORT_NAME = "garden-prx-ssh" export const systemDockerAuthSecretName = "builder-docker-config" export const dockerAuthSecretKey = ".dockerconfigjson" - export const skopeoDaemonContainerName = "util" - export const defaultIngressClass = "nginx" +export const rsyncPortName = "garden-rsync" +export const buildSyncVolumeName = `garden-sync` +export const k8sSyncUtilContainerName = "garden-sync-init" +export const buildkitDeploymentName = "garden-buildkit" +export const buildkitContainerName = "buildkitd" +export const defaultSystemNamespace = "garden-system" + +export const syncGuideRelPath = "guides/code-synchronization" +export const syncGuideLink = makeDocsLinkPlain(syncGuideRelPath) -// Docker images that Garden ships with -export const k8sUtilImageNameLegacy: DockerImageWithDigest = - "gardendev/k8s-util:0.5.7@sha256:522da245a5e6ae7c711aa94f84fc83f82a8fdffbf6d8bc48f4d80fee0e0e631b" -export const k8sUtilImageName: DockerImageWithDigest = - "gardendev/k8s-util:0.6.2@sha256:f51e7ce040e2e23bc0eaa7216e4d976f13786d96773ef7b8c8f349e7a63d74e9" +export const defaultUtilImageRegistryDomain = "docker.io" -export function getK8sUtilImageName(): DockerImageWithDigest { - return gardenEnv.GARDEN_ENABLE_NEW_SYNC ? k8sUtilImageName : k8sUtilImageNameLegacy +function makeImagePath({ + imageName, + registryDomain, +}: { + imageName: DockerImageWithDigest + registryDomain: string +}): DockerImageWithDigest { + const domainWithoutTrailingSlash = registryDomain.replace(/\/$/, "") + + return `${domainWithoutTrailingSlash}/${imageName}` } -export const k8sSyncUtilImageNameLegacy: DockerImageWithDigest = - "gardendev/k8s-sync:0.1.5@sha256:28263cee5ac41acebb8c08f852c4496b15e18c0c94797d7a949a4453b5f91578" -export const k8sSyncUtilImageName: DockerImageWithDigest = - "gardendev/k8s-sync:0.2.2@sha256:9ebcd84df4a3a55ae0ba95051cab521d249a4d2d7a15d04da7301c888c02347b" +export function getK8sUtilImagePath(registryDomain: string): DockerImageWithDigest { + const k8sUtilImageNameLegacy: DockerImageWithDigest = + "gardendev/k8s-util:0.5.7@sha256:522da245a5e6ae7c711aa94f84fc83f82a8fdffbf6d8bc48f4d80fee0e0e631b" + const k8sUtilImageName: DockerImageWithDigest = + "gardendev/k8s-util:0.6.2@sha256:f51e7ce040e2e23bc0eaa7216e4d976f13786d96773ef7b8c8f349e7a63d74e9" -export const k8sSyncUtilContainerName = "garden-sync-init" + return gardenEnv.GARDEN_ENABLE_NEW_SYNC + ? makeImagePath({ imageName: k8sUtilImageName, registryDomain }) + : makeImagePath({ imageName: k8sUtilImageNameLegacy, registryDomain }) +} + +export function getK8sSyncUtilImagePath(registryDomain: string): DockerImageWithDigest { + const k8sSyncUtilImageName: DockerImageWithDigest = + "gardendev/k8s-sync:0.2.2@sha256:9ebcd84df4a3a55ae0ba95051cab521d249a4d2d7a15d04da7301c888c02347b" + const k8sSyncUtilImageNameLegacy: DockerImageWithDigest = + "gardendev/k8s-sync:0.1.5@sha256:28263cee5ac41acebb8c08f852c4496b15e18c0c94797d7a949a4453b5f91578" -export function getK8sSyncUtilImageName(): DockerImageWithDigest { - return gardenEnv.GARDEN_ENABLE_NEW_SYNC ? k8sSyncUtilImageName : k8sSyncUtilImageNameLegacy + return gardenEnv.GARDEN_ENABLE_NEW_SYNC + ? makeImagePath({ imageName: k8sSyncUtilImageName, registryDomain }) + : makeImagePath({ imageName: k8sSyncUtilImageNameLegacy, registryDomain }) +} + +export function getK8sReverseProxyImagePath(registryDomain: string): DockerImageWithDigest { + const k8sReverseProxyImageName: DockerImageWithDigest = + "gardendev/k8s-reverse-proxy:0.1.1@sha256:2dff2275fc8c32cc0eba50eebd7ace6fdb007d9b3f4bd48d94355057324b2394" + + return makeImagePath({ imageName: k8sReverseProxyImageName, registryDomain }) +} +export function getBuildkitImagePath(registryDomain: string): DockerImageWithDigest { + const buildkitImageName: DockerImageWithDigest = + "gardendev/buildkit:v-0.16.0@sha256:ee7aa12e6fdba79ee9838631995fa7c5a12aba9091a0753dedfe891d430c8182" + + return makeImagePath({ imageName: buildkitImageName, registryDomain }) +} + +export function getBuildkitRootlessImagePath(registryDomain: string): DockerImageWithDigest { + const buildkitRootlessImageName: DockerImageWithDigest = + "gardendev/buildkit:v-0.16.0-rootless@sha256:634506c016691b079e44614c5de65e0b0d4a98070304f6089e15f0279bfca411" + + return makeImagePath({ imageName: buildkitRootlessImageName, registryDomain }) +} +export function getDefaultGardenIngressControllerDefaultBackendImagePath( + registryDomain: string +): DockerImageWithDigest { + const defaultGardenIngressControllerDefaultBackendImage: DockerImageWithDigest = + "gardendev/default-backend:v0.1@sha256:1b02920425eea569c6be53bb2e3d2c1182243212de229be375da7a93594498cf" + + return makeImagePath({ imageName: defaultGardenIngressControllerDefaultBackendImage, registryDomain }) } -export const k8sReverseProxyImageName: DockerImageWithDigest = - "gardendev/k8s-reverse-proxy:0.1.1@sha256:2dff2275fc8c32cc0eba50eebd7ace6fdb007d9b3f4bd48d94355057324b2394" -export const buildkitImageName: DockerImageWithDigest = - "gardendev/buildkit:v-0.16.0@sha256:ee7aa12e6fdba79ee9838631995fa7c5a12aba9091a0753dedfe891d430c8182" -export const buildkitRootlessImageName: DockerImageWithDigest = - "gardendev/buildkit:v-0.16.0-rootless@sha256:634506c016691b079e44614c5de65e0b0d4a98070304f6089e15f0279bfca411" export const defaultKanikoImageName: DockerImageWithDigest = "gcr.io/kaniko-project/executor:v1.11.0-debug@sha256:32ba2214921892c2fa7b5f9c4ae6f8f026538ce6b2105a93a36a8b5ee50fe517" -export const defaultGardenIngressControllerDefaultBackendImage: DockerImageWithDigest = - "gardendev/default-backend:v0.1@sha256:1b02920425eea569c6be53bb2e3d2c1182243212de229be375da7a93594498cf" export const defaultGardenIngressControllerImage: DockerImageWithDigest = "k8s.gcr.io/ingress-nginx/controller:v1.1.3@sha256:31f47c1e202b39fadecf822a9b76370bd4baed199a005b3e7d4d1455f4fd3fe2" export const defaultGardenIngressControllerKubeWebhookCertGenImage: DockerImageWithDigest = "k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1@sha256:64d8c73dca984af206adf9d6d7e46aa550362b1d7a01f3a0a91b20cc67868660" - -export const buildkitDeploymentName = "garden-buildkit" -export const buildkitContainerName = "buildkitd" -export const defaultSystemNamespace = "garden-system" - -export const syncGuideRelPath = "guides/code-synchronization" -export const syncGuideLink = makeDocsLinkPlain(syncGuideRelPath) diff --git a/core/src/plugins/kubernetes/container/build/buildkit.ts b/core/src/plugins/kubernetes/container/build/buildkit.ts index 5dff65246d..16c9d029a3 100644 --- a/core/src/plugins/kubernetes/container/build/buildkit.ts +++ b/core/src/plugins/kubernetes/container/build/buildkit.ts @@ -13,9 +13,9 @@ import { buildSyncVolumeName, buildkitContainerName, buildkitDeploymentName, - buildkitImageName, - buildkitRootlessImageName, dockerAuthSecretKey, + getBuildkitImagePath, + getBuildkitRootlessImagePath, } from "../../constants.js" import { KubeApi } from "../../api.js" import type { KubernetesDeployment } from "../../types.js" @@ -472,7 +472,7 @@ export function getBuildkitDeployment( containers: [ { name: buildkitContainerName, - image: buildkitImageName, + image: getBuildkitImagePath(provider.config.utilImageRegistryDomain), args: ["--addr", "unix:///run/buildkit/buildkitd.sock"], readinessProbe: { exec: { @@ -538,7 +538,7 @@ export function getBuildkitDeployment( "container.apparmor.security.beta.kubernetes.io/buildkitd": "unconfined", "container.seccomp.security.alpha.kubernetes.io/buildkitd": "unconfined", } - buildkitContainer.image = buildkitRootlessImageName + buildkitContainer.image = getBuildkitRootlessImagePath(provider.config.utilImageRegistryDomain) buildkitContainer.args = [ "--addr", "unix:///run/user/1000/buildkit/buildkitd.sock", diff --git a/core/src/plugins/kubernetes/container/build/common.ts b/core/src/plugins/kubernetes/container/build/common.ts index c04b91cd91..be729b7b01 100644 --- a/core/src/plugins/kubernetes/container/build/common.ts +++ b/core/src/plugins/kubernetes/container/build/common.ts @@ -29,7 +29,7 @@ import type { Resolved } from "../../../../actions/types.js" import { stringifyResources } from "../util.js" import { getKubectlExecDestination } from "../../sync.js" import { getRunningDeploymentPod } from "../../util.js" -import { buildSyncVolumeName, dockerAuthSecretKey, getK8sUtilImageName, rsyncPortName } from "../../constants.js" +import { buildSyncVolumeName, dockerAuthSecretKey, getK8sUtilImagePath, rsyncPortName } from "../../constants.js" import { styles } from "../../../../logger/styles.js" import type { StringMap } from "../../../../config/common.js" import { LogLevel } from "../../../../logger/logger.js" @@ -578,7 +578,7 @@ export function getBuilderServiceAccountSpec(namespace: string, annotations?: St export function getUtilContainer(authSecretName: string, provider: KubernetesProvider): V1Container { return { name: utilContainerName, - image: getK8sUtilImageName(), + image: getK8sUtilImagePath(provider.config.utilImageRegistryDomain), imagePullPolicy: "IfNotPresent", command: ["/rsync-server.sh"], env: [ diff --git a/core/src/plugins/kubernetes/container/build/kaniko.ts b/core/src/plugins/kubernetes/container/build/kaniko.ts index 961d49f673..a3f30f4120 100644 --- a/core/src/plugins/kubernetes/container/build/kaniko.ts +++ b/core/src/plugins/kubernetes/container/build/kaniko.ts @@ -11,7 +11,7 @@ import { skopeoDaemonContainerName, dockerAuthSecretKey, defaultKanikoImageName, - getK8sUtilImageName, + getK8sUtilImagePath, } from "../../constants.js" import { KubeApi } from "../../api.js" import type { Log } from "../../../../logger/log-entry.js" @@ -320,7 +320,7 @@ export function getKanikoBuilderPodManifest({ initContainers: [ { name: "init", - image: getK8sUtilImageName(), + image: getK8sUtilImagePath(provider.config.utilImageRegistryDomain), command: [ "/bin/sh", "-c", diff --git a/core/src/plugins/kubernetes/ephemeral/config.ts b/core/src/plugins/kubernetes/ephemeral/config.ts index c9fce1058c..31fb34f22f 100644 --- a/core/src/plugins/kubernetes/ephemeral/config.ts +++ b/core/src/plugins/kubernetes/ephemeral/config.ts @@ -17,7 +17,7 @@ import { ConfigurationError } from "../../../exceptions.js" import type { ConfigureProviderParams } from "../../../plugin/handlers/Provider/configureProvider.js" import { dedent } from "../../../util/string.js" import type { KubernetesConfig } from "../config.js" -import { defaultResources } from "../config.js" +import { defaultResources, utilImageRegistryDomainSpec } from "../config.js" import { namespaceSchema } from "../config.js" import { EPHEMERAL_KUBERNETES_PROVIDER_NAME } from "./ephemeral.js" import { DEFAULT_GARDEN_CLOUD_DOMAIN } from "../../../constants.js" @@ -30,6 +30,7 @@ export const configSchema = () => providerConfigBaseSchema() .keys({ name: joiProviderName(EPHEMERAL_KUBERNETES_PROVIDER_NAME), + utilImageRegistryDomain: utilImageRegistryDomainSpec, namespace: namespaceSchema().description( "Specify which namespace to deploy services to (defaults to the project name). " + "Note that the framework generates other namespaces as well with this name as a prefix." diff --git a/core/src/plugins/kubernetes/local-mode.ts b/core/src/plugins/kubernetes/local-mode.ts index 0406ec4a57..cc7b6900da 100644 --- a/core/src/plugins/kubernetes/local-mode.ts +++ b/core/src/plugins/kubernetes/local-mode.ts @@ -14,7 +14,7 @@ import { remove, set } from "lodash-es" import type { BaseResource, KubernetesResource, SyncableResource, SyncableRuntimeAction } from "./types.js" import type { PrimitiveMap } from "../../config/common.js" import { - k8sReverseProxyImageName, + getK8sReverseProxyImagePath, PROXY_CONTAINER_SSH_TUNNEL_PORT, PROXY_CONTAINER_SSH_TUNNEL_PORT_NAME, PROXY_CONTAINER_USER_NAME, @@ -382,16 +382,23 @@ function prepareLocalModePorts(): V1ContainerPort[] { * @param localModeEnvVars the list of localMode-specific environment variables * @param localModePorts the list of localMode-specific ports (e.g. ssh port for tunnel setup) */ -function patchSyncableManifest( - targetManifest: SyncableResource, - containerName: string, - localModeEnvVars: PrimitiveMap, +function patchSyncableManifest({ + targetManifest, + containerName, + localModeEnvVars, + localModePorts, + utilImageRegistryDomain, +}: { + targetManifest: SyncableResource + containerName: string + localModeEnvVars: PrimitiveMap localModePorts: V1ContainerPort[] -): void { + utilImageRegistryDomain: string +}): void { const targetContainer = getResourceContainer(targetManifest, containerName) // use reverse proxy container image - targetContainer.image = k8sReverseProxyImageName + targetContainer.image = getK8sReverseProxyImagePath(utilImageRegistryDomain) // erase the original container command, the proxy container won't recognize it targetContainer.command = [] // erase the original container arguments, the proxy container won't recognize them @@ -462,7 +469,7 @@ export async function configureLocalMode(configParams: ConfigureLocalModeParams) // Logging this on the debug level because it can be displayed multiple times due to getServiceStatus checks log.debug( - `Configuring in local mode, proxy container ${styles.underline(k8sReverseProxyImageName)} will be deployed.` + `Configuring in local mode, proxy container ${styles.underline(getK8sReverseProxyImagePath(provider.config.utilImageRegistryDomain))} will be deployed.` ) set(resolvedTarget, ["metadata", "annotations", gardenAnnotationKey("mode")], "local") @@ -478,7 +485,13 @@ export async function configureLocalMode(configParams: ConfigureLocalModeParams) const localModeEnvVars = await prepareLocalModeEnvVars(portSpecs, keyPair) const localModePorts = prepareLocalModePorts() - patchSyncableManifest(resolvedTarget, targetContainer.name, localModeEnvVars, localModePorts) + patchSyncableManifest({ + targetManifest: resolvedTarget, + containerName: targetContainer.name, + localModeEnvVars, + localModePorts, + utilImageRegistryDomain: provider.config.utilImageRegistryDomain, + }) // Replace the original resource with the modified spec const preparedManifests = manifests diff --git a/core/src/plugins/kubernetes/nginx/default-backend.ts b/core/src/plugins/kubernetes/nginx/default-backend.ts index a6ce6a9dd0..a0ace68874 100644 --- a/core/src/plugins/kubernetes/nginx/default-backend.ts +++ b/core/src/plugins/kubernetes/nginx/default-backend.ts @@ -12,7 +12,7 @@ import type { DeployState } from "../../../types/service.js" import { KubeApi } from "../api.js" import { checkResourceStatus, waitForResources } from "../status/status.js" import type { KubernetesDeployment, KubernetesService } from "../types.js" -import { defaultGardenIngressControllerDefaultBackendImage } from "../constants.js" +import { getDefaultGardenIngressControllerDefaultBackendImagePath } from "../constants.js" import { GardenIngressComponent } from "./ingress-controller-base.js" export class GardenDefaultBackend extends GardenIngressComponent { @@ -111,7 +111,7 @@ function defaultBackendGetManifests(ctx: KubernetesPluginContext): { spec: { containers: [ { - image: defaultGardenIngressControllerDefaultBackendImage, + image: getDefaultGardenIngressControllerDefaultBackendImagePath(provider.config.utilImageRegistryDomain), imagePullPolicy: "IfNotPresent", name: "default-backend", ports: [ diff --git a/core/src/plugins/kubernetes/sync.ts b/core/src/plugins/kubernetes/sync.ts index b851f5cad9..bd3153b8d0 100644 --- a/core/src/plugins/kubernetes/sync.ts +++ b/core/src/plugins/kubernetes/sync.ts @@ -58,7 +58,7 @@ import { isConfiguredForSyncMode } from "./status/status.js" import type { PluginContext } from "../../plugin-context.js" import type { SyncConfig, SyncSession } from "../../mutagen.js" import { haltedStatuses, Mutagen, mutagenAgentPath, mutagenStatusDescriptions } from "../../mutagen.js" -import { getK8sSyncUtilImageName, k8sSyncUtilContainerName, syncGuideLink } from "./constants.js" +import { getK8sSyncUtilImagePath, k8sSyncUtilContainerName, syncGuideLink } from "./constants.js" import { isAbsolute, relative, resolve } from "path" import type { Resolved } from "../../actions/types.js" import { joinWithPosix } from "../../util/fs.js" @@ -465,8 +465,8 @@ export async function configureSyncMode({ if (!podSpec.imagePullSecrets) { podSpec.imagePullSecrets = [] } - const k8sSyncUtilImageName = getK8sSyncUtilImageName() + const k8sSyncUtilImageName = getK8sSyncUtilImagePath(provider.config.utilImageRegistryDomain) if (!podSpec.initContainers.find((c) => c.image === k8sSyncUtilImageName)) { const initContainer: V1Container = { name: k8sSyncUtilContainerName, diff --git a/core/test/integ/src/plugins/kubernetes/container/deployment.ts b/core/test/integ/src/plugins/kubernetes/container/deployment.ts index 52fa94a8e2..399155ee6d 100644 --- a/core/test/integ/src/plugins/kubernetes/container/deployment.ts +++ b/core/test/integ/src/plugins/kubernetes/container/deployment.ts @@ -36,8 +36,9 @@ import { apply } from "../../../../../../src/plugins/kubernetes/kubectl.js" import { getAppNamespace } from "../../../../../../src/plugins/kubernetes/namespace.js" import { gardenAnnotationKey } from "../../../../../../src/util/string.js" import { - getK8sSyncUtilImageName, - k8sReverseProxyImageName, + defaultUtilImageRegistryDomain, + getK8sReverseProxyImagePath, + getK8sSyncUtilImagePath, k8sSyncUtilContainerName, PROXY_CONTAINER_SSH_TUNNEL_PORT, PROXY_CONTAINER_SSH_TUNNEL_PORT_NAME, @@ -122,7 +123,7 @@ describe("kubernetes container deployment handlers", () => { function expectProxyContainerImage(workload: KubernetesWorkload) { const appContainerSpec = workload.spec.template?.spec?.containers.find((c) => c.name === "local-mode") - expect(appContainerSpec!.image).to.eql(k8sReverseProxyImageName) + expect(appContainerSpec!.image).to.eql(getK8sReverseProxyImagePath(defaultUtilImageRegistryDomain)) } function expectContainerEnvVars(workload: KubernetesWorkload) { @@ -204,6 +205,53 @@ describe("kubernetes container deployment handlers", () => { }) }) + context("localMode with utilImageRegistryDomain set to a custom registry", () => { + const environmentName = "local" + let gardenCustomDomain: TestGarden + let cleanupCustomDomain: (() => void) | undefined + let ctxCustomDomain: KubernetesPluginContext + let providerCustomDomain: KubernetesProvider + let apiCustomDomain: KubeApi + + before(async () => { + const res = await getContainerTestGarden(environmentName) + gardenCustomDomain = res.garden + cleanup = res.cleanup + providerCustomDomain = <KubernetesProvider>( + await gardenCustomDomain.resolveProvider({ log: garden.log, name: "local-kubernetes" }) + ) + providerCustomDomain.config["utilImageRegistryDomain"] = "https://my-custom-registry-mirror.io" + + ctxCustomDomain = <KubernetesPluginContext>await garden.getPluginContext({ + provider: { + ...providerCustomDomain, + }, + templateContext: undefined, + events: undefined, + }) + apiCustomDomain = await KubeApi.factory(garden.log, ctx, provider) + }) + + it("should have proxy container image using custom container registry", async () => { + const action = await resolveDeployAction("local-mode", "local") // <---- + + const { workload } = await createContainerManifests({ + ctx: ctxCustomDomain, + api: apiCustomDomain, + action, + log: createActionLog({ log: gardenCustomDomain.log, actionName: action.name, actionKind: action.kind }), + imageId: getDeployedImageId(action), + }) + + const appContainerSpec = workload.spec.template?.spec?.containers.find((c) => c.name === "local-mode") + expect(appContainerSpec!.image).to.eql(getK8sReverseProxyImagePath("https://my-custom-registry-mirror.io")) + }) + + after(() => { + cleanupCustomDomain && cleanupCustomDomain() + }) + }) + context("localMode always takes precedence over syncMode", () => { it("Workflow should have ssh container port when in local mode", async () => { const action = await resolveDeployAction("local-mode", "local") // <---- @@ -452,7 +500,7 @@ describe("kubernetes container deployment handlers", () => { expect(resource.spec.template?.spec?.initContainers).to.eql([ { name: k8sSyncUtilContainerName, - image: getK8sSyncUtilImageName(), + image: getK8sSyncUtilImagePath(provider.config.utilImageRegistryDomain), command: ["/bin/sh", "-c", "'cp' '/usr/local/bin/mutagen-agent' '/.garden/mutagen-agent'"], imagePullPolicy: "IfNotPresent", volumeMounts: [ diff --git a/core/test/integ/src/plugins/kubernetes/container/ingress.ts b/core/test/integ/src/plugins/kubernetes/container/ingress.ts index 614987ace8..c4b728b330 100644 --- a/core/test/integ/src/plugins/kubernetes/container/ingress.ts +++ b/core/test/integ/src/plugins/kubernetes/container/ingress.ts @@ -27,7 +27,10 @@ import { actionFromConfig } from "../../../../../../src/graph/actions.js" import type { DeployAction } from "../../../../../../src/actions/deploy.js" import { DEFAULT_DEPLOY_TIMEOUT_SEC } from "../../../../../../src/constants.js" import { uuidv4 } from "../../../../../../src/util/random.js" -import { defaultSystemNamespace } from "../../../../../../src/plugins/kubernetes/constants.js" +import { + defaultSystemNamespace, + defaultUtilImageRegistryDomain, +} from "../../../../../../src/plugins/kubernetes/constants.js" const namespace = "my-namespace" const ports = [ @@ -43,6 +46,7 @@ type PartialConfig = PartialBy<KubernetesConfig, "context"> const basicConfig: PartialConfig = { name: "local-kubernetes", + utilImageRegistryDomain: defaultUtilImageRegistryDomain, buildMode: "local-docker", defaultHostname: "hostname.invalid", deploymentRegistry: { diff --git a/core/test/integ/src/plugins/kubernetes/sync-mode.ts b/core/test/integ/src/plugins/kubernetes/sync-mode.ts index bdb91caad2..e47fcbc970 100644 --- a/core/test/integ/src/plugins/kubernetes/sync-mode.ts +++ b/core/test/integ/src/plugins/kubernetes/sync-mode.ts @@ -34,7 +34,10 @@ import type { ContainerDeployActionConfig } from "../../../../../src/plugins/con import { resolveAction } from "../../../../../src/graph/actions.js" import { DeployTask } from "../../../../../src/tasks/deploy.js" import { MUTAGEN_DIR_NAME } from "../../../../../src/constants.js" -import { getK8sSyncUtilImageName } from "../../../../../src/plugins/kubernetes/constants.js" +import { + defaultUtilImageRegistryDomain, + getK8sSyncUtilImagePath, +} from "../../../../../src/plugins/kubernetes/constants.js" import type { Action, Resolved } from "../../../../../src/actions/types.js" import stripAnsi from "strip-ansi" @@ -551,7 +554,7 @@ describe("sync mode deployments and sync behavior", () => { initContainers: [ { name: "garden-sync-init", - image: getK8sSyncUtilImageName(), + image: getK8sSyncUtilImagePath(defaultUtilImageRegistryDomain), command: ["/bin/sh", "-c", "'cp' '/usr/local/bin/mutagen-agent' '/.garden/mutagen-agent'"], imagePullPolicy: "IfNotPresent", volumeMounts: [ @@ -601,7 +604,7 @@ describe("sync mode deployments and sync behavior", () => { initContainers: [ { name: "garden-sync-init", - image: getK8sSyncUtilImageName(), + image: getK8sSyncUtilImagePath(defaultUtilImageRegistryDomain), command: ["/bin/sh", "-c", "'cp' '/usr/local/bin/mutagen-agent' '/.garden/mutagen-agent'"], imagePullPolicy: "IfNotPresent", volumeMounts: [ @@ -1024,5 +1027,59 @@ describe("sync mode deployments and sync behavior", () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any expect((<any>res.updated[1]).spec.template.spec.containers[0].image).to.equal("new-image-2") }) + it("should use a custom container domain for the sync util image if configured that way", async () => { + const manifests = [ + { + kind: "Deployment", + apiVersion: "apps/v1", + metadata: { + name: "sync-mode", + }, + spec: { + template: { + spec: { + containers: [{ name: "sync-mode" }], + }, + }, + }, + }, + ] + + const res = await configureSyncMode({ + ctx, + log: actionLog, + provider: { + ...provider, + config: { + ...provider.config, + utilImageRegistryDomain: "https://my-custom-registry-mirror.io", + }, + }, + action, + manifests, + defaultTarget: undefined, + spec: { + paths: [ + { + target: { + kind: "Deployment", + name: "sync-mode", + }, + sourcePath: join(action.sourcePath(), "src"), + containerPath: "/app/src", + }, + ], + }, + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((<any>res.updated[0]).spec.template.spec.initContainers[0].image).to.eql( + getK8sSyncUtilImagePath("https://my-custom-registry-mirror.io") + ) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((<any>res.manifests[0]).spec.template.spec.initContainers[0].image).to.eql( + getK8sSyncUtilImagePath("https://my-custom-registry-mirror.io") + ) + }) }) }) diff --git a/core/test/unit/src/plugins/kubernetes/container/build/buildkit.ts b/core/test/unit/src/plugins/kubernetes/container/build/buildkit.ts index d4f9849f98..88fd283d65 100644 --- a/core/test/unit/src/plugins/kubernetes/container/build/buildkit.ts +++ b/core/test/unit/src/plugins/kubernetes/container/build/buildkit.ts @@ -14,7 +14,11 @@ import type { KubernetesProvider, } from "../../../../../../../src/plugins/kubernetes/config.js" import { defaultResources } from "../../../../../../../src/plugins/kubernetes/config.js" -import { buildkitImageName, getK8sUtilImageName } from "../../../../../../../src/plugins/kubernetes/constants.js" +import { + defaultUtilImageRegistryDomain, + getBuildkitImagePath, + getK8sUtilImagePath, +} from "../../../../../../../src/plugins/kubernetes/constants.js" import { getBuildkitDeployment, getBuildkitFlags, @@ -28,6 +32,7 @@ describe("buildkit build", () => { describe("getBuildkitDeployment", () => { const _provider: DeepPartial<KubernetesProvider> = { config: { + utilImageRegistryDomain: defaultUtilImageRegistryDomain, resources: defaultResources, }, } @@ -67,7 +72,7 @@ describe("buildkit build", () => { value: "/.docker", }, ], - image: buildkitImageName, + image: getBuildkitImagePath(provider.config.utilImageRegistryDomain), name: "buildkitd", readinessProbe: { exec: { @@ -114,7 +119,7 @@ describe("buildkit build", () => { value: "8730", }, ], - image: getK8sUtilImageName(), + image: getK8sUtilImagePath(provider.config.utilImageRegistryDomain), imagePullPolicy: "IfNotPresent", lifecycle: { preStop: { @@ -184,6 +189,27 @@ describe("buildkit build", () => { expect(result.metadata.annotations).eql(provider.config.clusterBuildkit.annotations) expect(result.spec.template.metadata?.annotations).eql(provider.config.clusterBuildkit.annotations) }) + + it("should use a custom container registry if set by user", () => { + const providerWithCustomRegistry = { + ...provider, + config: { + ...provider.config, + utilImageRegistryDomain: "https://my-custom-registry-mirror.io", + }, + } + + const result = getBuildkitDeployment(providerWithCustomRegistry, "authSecretName", [ + { name: "imagePullSecretName" }, + ]) + + expect(result.spec.template.spec?.containers[0].image).to.eql( + getBuildkitImagePath("https://my-custom-registry-mirror.io") + ) + expect(result.spec.template.spec?.containers[1].image).to.eql( + getK8sUtilImagePath("https://my-custom-registry-mirror.io") + ) + }) }) describe("getBuildkitFlags", () => { @@ -212,6 +238,7 @@ describe("buildkit build", () => { describe("makeBuildkitBuildCommand", () => { const _provider: DeepPartial<KubernetesProvider> = { config: { + utilImageRegistryDomain: defaultUtilImageRegistryDomain, resources: defaultResources, clusterBuildkit: { cache: [], diff --git a/core/test/unit/src/plugins/kubernetes/container/build/common.ts b/core/test/unit/src/plugins/kubernetes/container/build/common.ts index f4d5c49019..25cb1ff228 100644 --- a/core/test/unit/src/plugins/kubernetes/container/build/common.ts +++ b/core/test/unit/src/plugins/kubernetes/container/build/common.ts @@ -16,7 +16,10 @@ import { expect } from "chai" import type { KubernetesProvider } from "../../../../../../../src/plugins/kubernetes/config.js" import { defaultResources } from "../../../../../../../src/plugins/kubernetes/config.js" import type { DeepPartial } from "../../../../../../../src/util/util.js" -import { getK8sUtilImageName } from "../../../../../../../src/plugins/kubernetes/constants.js" +import { + defaultUtilImageRegistryDomain, + getK8sUtilImagePath, +} from "../../../../../../../src/plugins/kubernetes/constants.js" describe("common build", () => { describe("manifest error", () => { @@ -72,6 +75,7 @@ describe("common build", () => { describe("getUtilManifests", () => { const _provider: DeepPartial<KubernetesProvider> = { config: { + utilImageRegistryDomain: defaultUtilImageRegistryDomain, resources: { util: defaultResources.util, }, @@ -102,7 +106,7 @@ describe("common build", () => { containers: [ { name: "util", - image: getK8sUtilImageName(), + image: getK8sUtilImagePath(provider.config.utilImageRegistryDomain), imagePullPolicy: "IfNotPresent", command: ["/rsync-server.sh"], env: [ @@ -163,6 +167,21 @@ describe("common build", () => { }) }) + it("should use a custom registry mirror if configured by the user", () => { + const providerWithCustomRegistry = { + ...provider, + config: { + ...provider.config, + utilImageRegistryDomain: "https://my-custom-registry-mirror.io", + }, + } + + const result = getUtilManifests(providerWithCustomRegistry, "test", []) + expect(result.deployment.spec.template.spec?.containers[0].image).to.eql( + getK8sUtilImagePath("https://my-custom-registry-mirror.io") + ) + }) + it("should return the manifest with kaniko config tolerations if util tolerations are not specified", () => { const toleration = { key: "custom-kaniko-toleration", operator: "Equal", value: "true", effect: "NoSchedule" } provider.config.kaniko = { diff --git a/core/test/unit/src/plugins/kubernetes/container/build/kaniko.ts b/core/test/unit/src/plugins/kubernetes/container/build/kaniko.ts index 449c4ddcf2..158793a7ed 100644 --- a/core/test/unit/src/plugins/kubernetes/container/build/kaniko.ts +++ b/core/test/unit/src/plugins/kubernetes/container/build/kaniko.ts @@ -15,7 +15,11 @@ import { import { expect } from "chai" import type { KubernetesProvider } from "../../../../../../../src/plugins/kubernetes/config.js" import { defaultResources } from "../../../../../../../src/plugins/kubernetes/config.js" -import { defaultKanikoImageName, getK8sUtilImageName } from "../../../../../../../src/plugins/kubernetes/constants.js" +import { + defaultKanikoImageName, + defaultUtilImageRegistryDomain, + getK8sUtilImagePath, +} from "../../../../../../../src/plugins/kubernetes/constants.js" import type { DeepPartial } from "utility-types" import { inClusterBuilderServiceAccount } from "../../../../../../../src/plugins/kubernetes/container/build/common.js" @@ -60,6 +64,7 @@ describe("kaniko build", () => { describe("getKanikoBuilderPodManifest", () => { const _provider: DeepPartial<KubernetesProvider> = { config: { + utilImageRegistryDomain: defaultUtilImageRegistryDomain, kaniko: {}, resources: { ...defaultResources, @@ -132,7 +137,7 @@ describe("kaniko build", () => { "-c", 'echo "Copying from $SYNC_SOURCE_URL to $SYNC_CONTEXT_PATH"\nmkdir -p "$SYNC_CONTEXT_PATH"\nn=0\nuntil [ "$n" -ge 30 ]\ndo\n rsync \'arg1\' \'arg2\' && break\n n=$((n+1))\n sleep 1\ndone\necho "Done!"', ], - image: getK8sUtilImageName(), + image: getK8sUtilImagePath(provider.config.utilImageRegistryDomain), imagePullPolicy: "IfNotPresent", name: "init", volumeMounts: [ diff --git a/core/test/unit/src/plugins/kubernetes/init.ts b/core/test/unit/src/plugins/kubernetes/init.ts index 4a1fcceff4..593369436a 100644 --- a/core/test/unit/src/plugins/kubernetes/init.ts +++ b/core/test/unit/src/plugins/kubernetes/init.ts @@ -11,7 +11,11 @@ import { join } from "path" import * as td from "testdouble" import type { Garden } from "../../../../../src/garden.js" import { prepareDockerAuth, getIngressMisconfigurationWarnings } from "../../../../../src/plugins/kubernetes/init.js" -import { defaultSystemNamespace, dockerAuthSecretKey } from "../../../../../src/plugins/kubernetes/constants.js" +import { + defaultSystemNamespace, + defaultUtilImageRegistryDomain, + dockerAuthSecretKey, +} from "../../../../../src/plugins/kubernetes/constants.js" import { ConfigurationError } from "../../../../../src/exceptions.js" import type { KubernetesProvider, KubernetesConfig } from "../../../../../src/plugins/kubernetes/config.js" import { defaultResources } from "../../../../../src/plugins/kubernetes/config.js" @@ -27,6 +31,7 @@ import { uuidv4 } from "../../../../../src/util/random.js" const basicConfig: KubernetesConfig = { name: "kubernetes", + utilImageRegistryDomain: defaultUtilImageRegistryDomain, buildMode: "local-docker", context: "my-cluster", defaultHostname: "hostname.invalid", diff --git a/core/test/unit/src/plugins/kubernetes/kubernetes.ts b/core/test/unit/src/plugins/kubernetes/kubernetes.ts index 8ebc299ad9..0673bf8a8f 100644 --- a/core/test/unit/src/plugins/kubernetes/kubernetes.ts +++ b/core/test/unit/src/plugins/kubernetes/kubernetes.ts @@ -15,11 +15,15 @@ import { makeTempDir } from "../../../../helpers.js" import { providerFromConfig } from "../../../../../src/config/provider.js" import type { Garden } from "../../../../../src/garden.js" import { makeDummyGarden } from "../../../../../src/garden.js" -import { defaultSystemNamespace } from "../../../../../src/plugins/kubernetes/constants.js" +import { + defaultSystemNamespace, + defaultUtilImageRegistryDomain, +} from "../../../../../src/plugins/kubernetes/constants.js" describe("kubernetes configureProvider", () => { const basicConfig: KubernetesConfig = { name: "kubernetes", + utilImageRegistryDomain: defaultUtilImageRegistryDomain, buildMode: "local-docker", context: "my-cluster", defaultHostname: "hostname.invalid", diff --git a/docs/README.md b/docs/README.md index d504b2f3be..76adfc67a3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -60,6 +60,7 @@ * [AWS](./k8s-plugins/remote-k8s/configure-registry/aws.md) * [GCP](./k8s-plugins/remote-k8s/configure-registry/gcp.md) * [Azure](./k8s-plugins/remote-k8s/configure-registry/azure.md) + * [Docker Hub](./k8s-plugins/remote-k8s/configure-registry/docker-hub.md) * [3. Set Up Ingress, TLS and DNS](./k8s-plugins/remote-k8s/ingress-and-dns.md) * [4. Configure the Provider](./k8s-plugins/remote-k8s/configure-provider.md) * [Local K8s Plugin Configuration](./k8s-plugins/local-k8s/README.md) @@ -88,6 +89,7 @@ * [In-Cluster Building](./k8s-plugins/guides/in-cluster-building.md) * [Minimal RBAC Configuration for Development Clusters](./k8s-plugins/guides/rbac-config.md) * [Deploying to Production](./k8s-plugins/guides/deploying-to-production.md) + * [Using a Registry Mirror](./k8s-plugins/guides/registry-mirror.md) ## ☘️ Terraform Plugin diff --git a/docs/k8s-plugins/guides/registry-mirror.md b/docs/k8s-plugins/guides/registry-mirror.md new file mode 100644 index 0000000000..3907d377c5 --- /dev/null +++ b/docs/k8s-plugins/guides/registry-mirror.md @@ -0,0 +1,25 @@ +--- +title: Using a Registry Mirror +order: 4 +--- + +# Using a Registry Mirror + +Garden uses a handful of utility container images that are hosted on [Docker Hub](https://hub.docker.com/) under the `gardendev` repository. These are used for various Kubernetes tasks such as managing syncs and are usually deployed into a given project namespace along with the rest of the project services. + +If you have your own Docker Hub registry mirror you can configure Garden to use that instead of Docker Hub. Using your own registry mirror can improve performance because the mirror is typically in your VPC and prevents you from being rate limited by Docker Hub (see also [this FAQ entry](../../misc/faq.md#how-do-i-avoid-being-rate-limited-by-docker-hub) on Docker Hub rate limiting). + +To tell Garden to use your custom registry mirror instead of Docker Hub, set the `utilImageRegistryDomain` field on the Kubernetes provider, for example: + +```yaml +kind: Project +name: my-project +#... +providers: + - name: kubernetes + utilImageRegistryDomain: https://<my-private-registry-domain> +``` + +This option is available on all the Kubernetes plugins (i.e. `local-kubernetes`, `ephemeral-kubernetes`, and `kubernetes`). + +Now when you run a Garden command, the utility images will be pulled from the registry mirror. diff --git a/docs/k8s-plugins/remote-k8s/configure-provider.md b/docs/k8s-plugins/remote-k8s/configure-provider.md index f998e84810..45e37afd56 100644 --- a/docs/k8s-plugins/remote-k8s/configure-provider.md +++ b/docs/k8s-plugins/remote-k8s/configure-provider.md @@ -42,7 +42,11 @@ providers: context: <THE KUBE CONTEXT FROM STEP 1> defaultHostname: <THE HOSTNAME FROM STEP 3> ``` -**NOTE:** If you are using `kubernetes deploy` the `imagePullSecret` won't be automatically propagated to manifests. Make sure to use `imagePullSecrets` in your manifest as specified in kubernetes [docs](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#create-a-pod-that-uses-your-secret). + +{% hint style="warning" %} +Garden does NOT inject the image pull secret into the Deployment (unless you're using the `container` Deploy type). So if you're using e.g. the `kubernetes` or `helm` action types you need to make sure the `imagePullSecret` field is set in the corresponding manifest / Helm chart. See also the [official Kubernetes docs for setting image pull secrets](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#create-a-pod-that-uses-your-secret). +{% endhint %} + ### 2. Select build mode Next, select a "build mode". diff --git a/docs/k8s-plugins/remote-k8s/configure-registry/README.md b/docs/k8s-plugins/remote-k8s/configure-registry/README.md index e0c03bf918..32ddad2cc8 100644 --- a/docs/k8s-plugins/remote-k8s/configure-registry/README.md +++ b/docs/k8s-plugins/remote-k8s/configure-registry/README.md @@ -25,6 +25,7 @@ Below you'll find guides for specific cloud providers: * [AWS](./aws.md) * [GCP](./gcp.md) * [Azure](./azure.md) +* [Docker Hub](./docker-hub.md) As always, feel free to pick a different approach. The end goal having a container registry that Garden can push to and that your cluster can pull from. diff --git a/docs/k8s-plugins/remote-k8s/configure-registry/docker-hub.md b/docs/k8s-plugins/remote-k8s/configure-registry/docker-hub.md new file mode 100644 index 0000000000..648ca91b22 --- /dev/null +++ b/docs/k8s-plugins/remote-k8s/configure-registry/docker-hub.md @@ -0,0 +1,51 @@ +--- +title: Docker Hub +order: 4 +--- + +# Docker Hub + +To pull and push images from private Docker Hub repositories you need to create an image pull secret for Docker Hub. Creating an image pull secret for Docker Hub also reduces the chance of being [rate limited](../../../misc/faq.md#how-do-i-avoid-being-rate-limited-by-docker-hub) (e.g. when deploying Garden utility images). + + +{% hint style="info" %} +For a more in-depth guide on creating image pull secrets, check out the [official Kubernetes documentation](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/). +{% endhint %} + +### Step 1 — Log in + +Log in to the Docker Hub account you want to use with: + +```sh +docker login +``` + +The login process creates or updates a `config.json` file that holds an authorization token. You can view it with: + +```sh +cat ~/.docker/config.json +``` + +The output contains a section similar to this: + +```json +{ + "auths": { + "https://index.docker.io/v1/": { + "auth": "c3R...zE2" + } + } +} +``` + +### Step 2 — Create the secret + +You can now create the image pull secret with the following command: + +``` +kubectl create secret generic regcred \ + --from-file=.dockerconfigjson=<path/to/.docker/config.json> \ + --type=kubernetes.io/dockerconfigjson +``` + +Here we're creating a secret called `regcred` in the `default` namespace. Take note of the name and namespace as you'll need it when configuring the Kubernetes provider in [step 4](../configure-provider.md). diff --git a/docs/misc/faq.md b/docs/misc/faq.md index 2331f81775..2d92192780 100644 --- a/docs/misc/faq.md +++ b/docs/misc/faq.md @@ -250,6 +250,49 @@ Garden interfaces with your cluster via `kubectl` and by using the Kubernetes AP No, you have to use the [`kubernetes`](../k8s-plugins/actions/deploy/kubernetes.md) action type for that. +### How do I avoid being rate limited by Docker Hub? + +Garden uses a handful of utility images that are hosted on [Docker Hub](https://hub.docker.com) under the `gardendev` repository and under heavy usage, users can get rate limited when deploying them. + +We're in the process of applying for becoming a Verified Docker Publisher which should significantly reduce the chance of being rate limited. + +In the meantime, you have the following options: + +**Option 1 — Crate a Docker Hub image pull secret:** + +First follow the steps in [this guide](../k8s-plugins/remote-k8s/configure-registry/docker-hub.md) to create an image pull secret for +Docker Hub. + +Then add the name and namespace of the secret you created to the `imagePullSecrets` field of the Kubernetes provider: + +```yaml +kind: Project +name: my-project +#... +providers: + - name: kubernetes + imagePullSecrets: + - name: <the-secret-name> + namespace: <the-secret-namespace> +``` + +This also works for the `local-kubernetes` and `ephemeral-kubernetes` providers. + +**Option 2 — Use a registry mirror:** + +If you already have your own Docker Hub registry mirror set up you can use that by setting the `utilImageRegistryDomain` field on the Kubernetes provider: + +```yaml +kind: Project +name: my-project +#... +providers: + - name: kubernetes + utilImageRegistryDomain: https://<my-private-registry-domain> +``` + +This also works for the `local-kubernetes` and `ephemeral-kubernetes` providers. + ## Local scripts ### How do I execute long running local scripts? diff --git a/docs/reference/providers/ephemeral-kubernetes.md b/docs/reference/providers/ephemeral-kubernetes.md index e21af35c62..92eea210f7 100644 --- a/docs/reference/providers/ephemeral-kubernetes.md +++ b/docs/reference/providers/ephemeral-kubernetes.md @@ -35,6 +35,15 @@ providers: # The name of the provider plugin to use. name: ephemeral-kubernetes + # The container registry domain that should be used for pulling Garden utility images (such as the + # image used in the Kubernetes sync utility Pod). + # + # If you have your own Docker Hub registry mirror, you can set the domain here and the utility images + # will be pulled from there. This can be useful to e.g. avoid Docker Hub rate limiting. + # + # Otherwise the utility images are pulled directly from Docker Hub by default. + utilImageRegistryDomain: docker.io + # Specify which namespace to deploy services to (defaults to the project name). Note that the framework generates # other namespaces as well with this name as a prefix. namespace: @@ -115,6 +124,22 @@ providers: - name: "ephemeral-kubernetes" ``` +### `providers[].utilImageRegistryDomain` + +[providers](#providers) > utilImageRegistryDomain + +The container registry domain that should be used for pulling Garden utility images (such as the +image used in the Kubernetes sync utility Pod). + +If you have your own Docker Hub registry mirror, you can set the domain here and the utility images +will be pulled from there. This can be useful to e.g. avoid Docker Hub rate limiting. + +Otherwise the utility images are pulled directly from Docker Hub by default. + +| Type | Default | Required | +| -------- | ------------- | -------- | +| `string` | `"docker.io"` | No | + ### `providers[].namespace` [providers](#providers) > namespace diff --git a/docs/reference/providers/kubernetes.md b/docs/reference/providers/kubernetes.md index cc64e91fa1..bb348c0124 100644 --- a/docs/reference/providers/kubernetes.md +++ b/docs/reference/providers/kubernetes.md @@ -37,6 +37,15 @@ providers: # disables the provider. To use a provider in all environments, omit this field. environments: + # The container registry domain that should be used for pulling Garden utility images (such as the + # image used in the Kubernetes sync utility Pod). + # + # If you have your own Docker Hub registry mirror, you can set the domain here and the utility images + # will be pulled from there. This can be useful to e.g. avoid Docker Hub rate limiting. + # + # Otherwise the utility images are pulled directly from Docker Hub by default. + utilImageRegistryDomain: docker.io + # Choose the mechanism for building container images before deploying. By default your local Docker daemon is # used, but you can set it to `cluster-buildkit` or `kaniko` to sync files to the cluster, and build container # images there. This removes the need to run Docker locally, and allows you to share layer and image caches @@ -573,6 +582,22 @@ providers: - stage ``` +### `providers[].utilImageRegistryDomain` + +[providers](#providers) > utilImageRegistryDomain + +The container registry domain that should be used for pulling Garden utility images (such as the +image used in the Kubernetes sync utility Pod). + +If you have your own Docker Hub registry mirror, you can set the domain here and the utility images +will be pulled from there. This can be useful to e.g. avoid Docker Hub rate limiting. + +Otherwise the utility images are pulled directly from Docker Hub by default. + +| Type | Default | Required | +| -------- | ------------- | -------- | +| `string` | `"docker.io"` | No | + ### `providers[].buildMode` [providers](#providers) > buildMode diff --git a/docs/reference/providers/local-kubernetes.md b/docs/reference/providers/local-kubernetes.md index f6c222a1bf..3e940dbd14 100644 --- a/docs/reference/providers/local-kubernetes.md +++ b/docs/reference/providers/local-kubernetes.md @@ -30,6 +30,15 @@ providers: # disables the provider. To use a provider in all environments, omit this field. environments: + # The container registry domain that should be used for pulling Garden utility images (such as the + # image used in the Kubernetes sync utility Pod). + # + # If you have your own Docker Hub registry mirror, you can set the domain here and the utility images + # will be pulled from there. This can be useful to e.g. avoid Docker Hub rate limiting. + # + # Otherwise the utility images are pulled directly from Docker Hub by default. + utilImageRegistryDomain: docker.io + # Choose the mechanism for building container images before deploying. By default your local Docker daemon is # used, but you can set it to `cluster-buildkit` or `kaniko` to sync files to the cluster, and build container # images there. This removes the need to run Docker locally, and allows you to share layer and image caches @@ -518,6 +527,22 @@ providers: - stage ``` +### `providers[].utilImageRegistryDomain` + +[providers](#providers) > utilImageRegistryDomain + +The container registry domain that should be used for pulling Garden utility images (such as the +image used in the Kubernetes sync utility Pod). + +If you have your own Docker Hub registry mirror, you can set the domain here and the utility images +will be pulled from there. This can be useful to e.g. avoid Docker Hub rate limiting. + +Otherwise the utility images are pulled directly from Docker Hub by default. + +| Type | Default | Required | +| -------- | ------------- | -------- | +| `string` | `"docker.io"` | No | + ### `providers[].buildMode` [providers](#providers) > buildMode