Skip to content

Commit

Permalink
feat(k8s): add support for KinD
Browse files Browse the repository at this point in the history
* feat(kind): load images into kind nodes

* chore(lint): fixed linting issues

* feat(kind): used kubeApi

* chore(kind): fixed linter warnings

* chore(feedback): fix indentation and return type

* chore(docs): updated the docs

* chore(error): improve exception handling
  • Loading branch information
solomonope authored and edvald committed Dec 20, 2019
1 parent b2b30b1 commit 87a6978
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 12 deletions.
1 change: 1 addition & 0 deletions garden-service/src/plugins/kubernetes/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export interface KubernetesConfig extends ProviderConfig {
gardenSystemNamespace: string
tlsCertificates: IngressTlsCertificate[]
certManager?: CertManagerConfig
clusterType?: "kind" | "minikube" | "microk8s"
_systemServices: string[]
}

Expand Down
6 changes: 5 additions & 1 deletion garden-service/src/plugins/kubernetes/container/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { posix, resolve } from "path"
import { KubeApi } from "../api"
import { kubectl } from "../kubectl"
import { LogEntry } from "../../../logger/log-entry"
import { KubernetesProvider, ContainerBuildMode, KubernetesPluginContext } from "../config"
import { KubernetesProvider, ContainerBuildMode, KubernetesPluginContext, KubernetesConfig } from "../config"
import { PluginError } from "../../../exceptions"
import { PodRunner } from "../run"
import { getRegistryHostname, getKubernetesSystemVariables } from "../init"
Expand All @@ -29,6 +29,7 @@ import { getPortForward } from "../port-forward"
import { Writable } from "stream"
import { LogLevel } from "../../../logger/log-node"
import { exec, renderOutputStream } from "../../../util/util"
import { loadLocalImage } from "../local/kind"

const dockerDaemonDeploymentName = "garden-docker-daemon"
const dockerDaemonContainerName = "docker-daemon"
Expand Down Expand Up @@ -96,6 +97,9 @@ const localBuild: BuildHandler = async (params) => {
const buildResult = await buildContainerModule(params)

if (!ctx.provider.config.deploymentRegistry) {
if ((ctx.provider.config as KubernetesConfig).clusterType === "kind") {
await loadLocalImage(buildResult, ctx.provider.config as KubernetesConfig)
}
return buildResult
}

Expand Down
30 changes: 19 additions & 11 deletions garden-service/src/plugins/kubernetes/local/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { setMinikubeDockerEnv } from "./minikube"
import { exec } from "../../../util/util"
import { remove } from "lodash"
import { getNfsStorageClass } from "../init"
import { isClusterKind } from "./kind"

// TODO: split this into separate plugins to handle Docker for Mac and Minikube

Expand Down Expand Up @@ -53,19 +54,19 @@ export async function configureProvider(params: ConfigureProviderParams<LocalKub
const namespace = config.namespace!
const _systemServices = config._systemServices

// create dummy provider with just enough info needed for the getKubeConfig function
const provider = {
name: config.name,
dependencies: [],
config,
moduleConfigs: [],
status: { ready: true, outputs: {} },
}
const kubeConfig = await getKubeConfig(log, provider)
const currentContext = kubeConfig["current-context"]

if (!config.context) {
// automatically detect supported kubectl context if not explicitly configured
// create dummy provider with just enough info needed for the getKubeConfig function
const provider = {
name: config.name,
dependencies: [],
config,
moduleConfigs: [],
status: { ready: true, outputs: {} },
}
const kubeConfig = await getKubeConfig(log, provider)
const currentContext = kubeConfig["current-context"]

if (currentContext && supportedContexts.includes(currentContext)) {
// prefer current context if set and supported
config.context = currentContext
Expand Down Expand Up @@ -96,9 +97,14 @@ export async function configureProvider(params: ConfigureProviderParams<LocalKub
log.debug({ section: config.name, msg: `No kubectl context configured, using default: ${config.context}` })
}

if (await isClusterKind(provider, log)) {
config.clusterType = "kind"
}
if (config.context === "minikube") {
await exec("minikube", ["config", "set", "WantUpdateNotification", "false"])

config.clusterType = "minikube"

if (!config.defaultHostname) {
// use the nip.io service to give a hostname to the instance, if none is explicitly configured
const { stdout } = await exec("minikube", ["ip"])
Expand All @@ -115,6 +121,8 @@ export async function configureProvider(params: ConfigureProviderParams<LocalKub
} else if (config.context === "microk8s") {
const addons = ["dns", "registry", "storage"]

config.clusterType = "microk8s"

if (config.setupIngressController === "nginx") {
log.debug("Using microk8s's ingress addon")
addons.push("ingress")
Expand Down
94 changes: 94 additions & 0 deletions garden-service/src/plugins/kubernetes/local/kind.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright (C) 2018 Garden Technologies, Inc. <[email protected]>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { exec } from "../../../util/util"
import { LogEntry } from "../../../logger/log-entry"
import { safeLoad } from "js-yaml"
import { BuildResult } from "../../../types/plugin/module/build"
import { KubernetesConfig, KubernetesProvider } from "../config"
import { RuntimeError } from "../../../exceptions"
import { KubeApi } from "../api"
import { KubernetesResource } from "../types"

export async function loadLocalImage(buildResult: BuildResult, config: KubernetesConfig): Promise<void> {
try {
const clusterName = await getClusterForContext(config.context)
if (clusterName !== null) {
await exec("kind", ["load", "docker-image", buildResult.details.identifier, `--name=${clusterName}`])
}
} catch (err) {
throw new RuntimeError(
`An attempt to load image ${buildResult.details.identifier} into the kind cluster failed: ${err.message}`,
{ err }
)
}
}

export async function isClusterKind(provider: KubernetesProvider, log: LogEntry): Promise<boolean> {
return (await isKindInstalled(log)) && (await isKindContext(log, provider))
}

async function isKindInstalled(log: LogEntry): Promise<boolean> {
try {
const kindVersion = (await exec("kind", ["version"])).stdout
log.debug(`Found kind with the following version details ${kindVersion}`)
return true
} catch (err) {
log.debug(`An attempt to get kind version failed with ${err}`)
}

return false
}

async function isKindContext(log: LogEntry, provider: KubernetesProvider): Promise<boolean> {
const kubeApi = await KubeApi.factory(log, provider)
const manifest: KubernetesResource = {
apiVersion: "apps/v1",
kind: "DaemonSet",
metadata: {
name: "kindnet",
},
}
try {
await kubeApi.readBySpec("kube-system", manifest, log)
return true
} catch (err) {
log.debug(`An attempt to get kindnet deamonset failed with ${err}`)
}

return false
}

async function getKindClusters(): Promise<Array<string>> {
try {
const clusters = (await exec("kind", ["get", "clusters"])).stdout
if (clusters) {
return clusters.split("\n")
}
return []
} catch (err) {}
return []
}

async function getClusterForContext(context: string) {
for (let cluster of await getKindClusters()) {
if (await isContextAMatch(cluster, context)) {
return cluster
}
}
return null
}

async function isContextAMatch(cluster: string, context: string): Promise<Boolean> {
try {
const kubeConfigString = (await exec("kind", ["get", "kubeconfig", `--name=${cluster}`])).stdout
const kubeConfig = safeLoad(kubeConfigString)
return kubeConfig["current-context"] === context
} catch (err) {}
return false
}

0 comments on commit 87a6978

Please sign in to comment.