Skip to content

Commit

Permalink
feat(k8s): add microk8s support
Browse files Browse the repository at this point in the history
Closes #406
  • Loading branch information
edvald authored and thsig committed Apr 18, 2019
1 parent 47be669 commit e113c69
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 32 deletions.
27 changes: 27 additions & 0 deletions docs/basics/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,33 @@ Use your preferred method or package manager to install `git` and `rsync`. On Ub

Once you have the dependencies set up, download the Garden CLI for your platform from our [latest release](https://github.com/garden-io/garden/releases/latest) page, extract and make sure it is on your PATH. E.g. by extracting to `~/.garden/bin` and adding `export PATH=$PATH:~/.garden/bin` to your `.bashrc` or `.zshrc` file.

## MicroK8s

Garden can be used with [MicroK8s](https://microk8s.io) on supported Linux platforms.

To install it, please follow [their instructions](https://microk8s.io/docs/).

Once installed, note that you need to make sure Garden can access the cluster by either aliasing `microk8s.kubectl` to
`kubectl`:

```sh
alias kubectl='microk8s.kubectl'
```

_Or_ if you already have `kubectl` installed (or wish to install it separately), you need to add the `microk8s`
configuration to your `~/.kube/config` so that Garden knows how to access your cluster. We recommend exporting the
config like this:

```sh
microk8s.kubectl config view --raw > $HOME/.kube/microk8s.config
```

And then adding this to your `.bashrc`/`.zshrc`:

```sh
export KUBECONFIG=${KUBECONFIG:-$HOME/.kube/config}:$HOME/.kube/microk8s.config
```

## Minikube

Garden can be used with [Minikube](https://github.com/kubernetes/minikube) on supported platforms.
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/plugins/kubernetes/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ export class KubeApi {
return Reflect.get(target, name, receiver)
}

return function (...args) {
return function(...args) {
const defaultHeaders = target["defaultHeaders"]

if (name.startsWith("patch")) {
Expand Down
7 changes: 5 additions & 2 deletions garden-service/src/plugins/kubernetes/container/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export async function createDeployment(
valueFrom: { fieldRef: { fieldPath: "status.podIP" } },
})

const registryConfig = provider.name === "local-kubernetes" ? undefined : provider.config.deploymentRegistry
const registryConfig = provider.config.deploymentRegistry
const imageId = await containerHelpers.getDeploymentImageId(service.module, registryConfig)

const container: any = {
Expand Down Expand Up @@ -423,8 +423,11 @@ export async function deleteContainerDeployment(
}

export async function pushModule({ ctx, module, log }: PushModuleParams<ContainerModule>) {
if (!ctx.provider.config.deploymentRegistry) {
return { pushed: false }
}

if (!(await containerHelpers.hasDockerfile(module))) {
log.setState({ msg: `Nothing to push` })
return { pushed: false }
}

Expand Down
51 changes: 30 additions & 21 deletions garden-service/src/plugins/kubernetes/local/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,15 @@ import { KubernetesBaseConfig, kubernetesConfigBase } from "../kubernetes"
import { ConfigureProviderParams } from "../../../types/plugin/params"
import { joiProviderName } from "../../../config/common"
import { getKubeConfig } from "../api"
import { configureMicrok8sAddons } from "./microk8s"
import { setMinikubeDockerEnv } from "./minikube"
import { ContainerRegistryConfig } from "../../container/config"

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

// note: this is in order of preference, in case neither is set as the current kubectl context
// and none is explicitly configured in the garden.yml
const supportedContexts = ["docker-for-desktop", "minikube"]

/**
* Automatically set docker environment variables for minikube
* TODO: it would be better to explicitly provide those to docker instead of using process.env
*/
async function setMinikubeDockerEnv() {
const minikubeEnv = await execa.stdout("minikube", ["docker-env", "--shell=bash"])
for (const line of minikubeEnv.split("\n")) {
const matched = line.match(/^export (\w+)="(.+)"$/)
if (matched) {
process.env[matched[1]] = matched[2]
}
}
}
const supportedContexts = ["docker-for-desktop", "microk8s", "minikube"]

export interface LocalKubernetesConfig extends KubernetesBaseConfig {
_system?: Symbol
Expand All @@ -58,7 +47,9 @@ export const configSchema = kubernetesConfigBase
export async function configureProvider({ config, log, projectName }: ConfigureProviderParams<LocalKubernetesConfig>) {
let context = config.context
let defaultHostname = config.defaultHostname
let deploymentRegistry: ContainerRegistryConfig | undefined = undefined
let setupIngressController = config.setupIngressController
const namespace = config.namespace || projectName

if (!context) {
// automatically detect supported kubectl context if not explicitly configured
Expand All @@ -69,7 +60,7 @@ export async function configureProvider({ config, log, projectName }: ConfigureP
// prefer current context if set and supported
context = currentContext
log.debug({ section: config.name, msg: `Using current context: ${context}` })
} else if (kubeConfig.contexts) {
} else {
const availableContexts = kubeConfig.contexts.map(c => c.name)

for (const supportedContext of supportedContexts) {
Expand Down Expand Up @@ -102,18 +93,35 @@ export async function configureProvider({ config, log, projectName }: ConfigureP
}

if (config.setupIngressController === "nginx") {
log.silly("Using minikube's ingress addon")
log.debug("Using minikube's ingress addon")
await execa("minikube", ["addons", "enable", "ingress"])
// make sure the prepare handler doesn't also set up the ingress controller
setupIngressController = null
}

await setMinikubeDockerEnv()

} else {
if (!defaultHostname) {
defaultHostname = `${projectName}.local.app.garden`
} else if (context === "microk8s") {
const addons = ["dns", "dashboard", "registry", "storage"]

if (config.setupIngressController === "nginx") {
log.debug("Using microk8s's ingress addon")
addons.push("ingress")
// make sure the prepare handler doesn't also set up the ingress controller
setupIngressController = null
}

await configureMicrok8sAddons(log, addons)

// Need to push to the built-in registry
deploymentRegistry = {
hostname: "localhost:32000",
namespace,
}
}

if (!defaultHostname) {
defaultHostname = `${projectName}.local.app.garden`
}

const ingressClass = config.ingressClass || config.setupIngressController || undefined
Expand All @@ -122,12 +130,13 @@ export async function configureProvider({ config, log, projectName }: ConfigureP
name: config.name,
context,
defaultHostname,
deploymentRegistry,
forceSsl: false,
imagePullSecrets: config.imagePullSecrets,
ingressHttpPort: 80,
ingressHttpsPort: 443,
ingressClass,
namespace: config.namespace || projectName,
namespace,
setupIngressController,
tlsCertificates: config.tlsCertificates,
_system: config._system,
Expand Down
4 changes: 0 additions & 4 deletions garden-service/src/plugins/kubernetes/local/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,5 @@ export function gardenPlugin(): GardenPlugin {
plugin.actions!.getEnvironmentStatus = getLocalEnvironmentStatus
plugin.actions!.prepareEnvironment = prepareLocalEnvironment

// no need to push before deploying locally
delete plugin.moduleActions!.container.pushModule
delete plugin.moduleActions!["maven-container"].pushModule

return plugin
}
34 changes: 34 additions & 0 deletions garden-service/src/plugins/kubernetes/local/microk8s.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 * as execa from "execa"
import { RuntimeError } from "../../../exceptions"
import { LogEntry } from "../../../logger/log-entry"

export async function configureMicrok8sAddons(log: LogEntry, addons: string[]) {
let status = ""

try {
status = await execa.stdout("microk8s.status")
} catch {
// This is caught below.
}

if (!status.includes("microk8s is running")) {
throw new RuntimeError(`Unable to get microk8s status. Is the cluster installed and running?`, {
status,
})
}

const missingAddons = addons.filter(addon => !status.includes(`${addon}: enabled`))

if (missingAddons.length > 0) {
log.info({ section: "microk8s", msg: `enabling required addons (${missingAddons.join(", ")})` })
await execa("microk8s.enable", missingAddons)
}
}
23 changes: 23 additions & 0 deletions garden-service/src/plugins/kubernetes/local/minikube.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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 * as execa from "execa"

/**
* Automatically set docker environment variables for minikube
* TODO: it would be better to explicitly provide those to docker instead of using process.env
*/
export async function setMinikubeDockerEnv() {
const minikubeEnv = await execa.stdout("minikube", ["docker-env", "--shell=bash"])
for (const line of minikubeEnv.split("\n")) {
const matched = line.match(/^export (\w+)="(.+)"$/)
if (matched) {
process.env[matched[1]] = matched[2]
}
}
}
8 changes: 4 additions & 4 deletions garden-service/src/tasks/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export class PushTask extends BaseTask {

const log = this.log.info({
section: this.module.name,
msg: "Pushing",
msg: "Pushing...",
status: "active",
})

Expand All @@ -105,10 +105,10 @@ export class PushTask extends BaseTask {
throw err
}

if (result.pushed) {
log.setSuccess({ msg: chalk.green(result.message || `Ready`), append: true })
} else if (result.message) {
if (result.message) {
log.setWarn({ msg: result.message, append: true })
} else {
log.setSuccess({ msg: chalk.green(result.message || `Done (took ${log.getDuration(1)} sec)`), append: true })
}

return result
Expand Down

0 comments on commit e113c69

Please sign in to comment.