From c06bafac2538a2ca2a7c0c78e88c1484340c9a60 Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Wed, 14 Jun 2017 16:05:26 -0700 Subject: [PATCH] rewrite `draft init` --- api/server.go | 3 +- chart/values.yaml | 4 +- cmd/draft/draft.go | 21 ++- cmd/draft/init.go | 231 +++++++++++++-------------- cmd/draft/installer/config/config.go | 29 ++++ cmd/draft/installer/install.go | 4 +- docs/contributing/hacking.md | 62 +------ docs/design.md | 22 +-- docs/getting-started.md | 5 +- docs/ingress.md | 2 +- docs/install.md | 129 ++++++++++++--- docs/packs.md | 15 +- docs/plugins.md | 6 +- docs/user-guide.md | 10 +- 14 files changed, 297 insertions(+), 246 deletions(-) create mode 100644 cmd/draft/installer/config/config.go diff --git a/api/server.go b/api/server.go index e86ebf5..f28d0b3 100644 --- a/api/server.go +++ b/api/server.go @@ -388,8 +388,7 @@ func buildApp(ws *websocket.Conn, server *Server, appName string, buildContext i // Break up registry auth json string into a RegistryAuth object. var regAuth RegistryAuth - err = json.Unmarshal(data, ®Auth) - if err != nil { + if err := json.Unmarshal(data, ®Auth); err != nil { handleClosingError(ws, "Could not json decode registry authentication string", err) } diff --git a/chart/values.yaml b/chart/values.yaml index a975372..dbcca01 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -16,7 +16,7 @@ service: internalPort: 44135 registry: url: docker.io - org: microsoft + org: draft # This field follows the format of Docker's X-Registry-Auth header. # # See https://github.com/docker/docker/blob/master/docs/api/v1.22.md#push-an-image-on-the-registry @@ -28,4 +28,4 @@ registry: # For token-based logins, use # # $ echo '{"registrytoken":"9cbaf023786cd7"}' | base64 - authtoken: changeme + authtoken: e30K diff --git a/cmd/draft/draft.go b/cmd/draft/draft.go index 3bffed8..10b788d 100644 --- a/cmd/draft/draft.go +++ b/cmd/draft/draft.go @@ -14,7 +14,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/spf13/cobra" "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" "k8s.io/helm/pkg/kube" "github.com/Azure/draft/pkg/draft" @@ -43,7 +43,7 @@ var ( var globalUsage = `The application deployment tool for Kubernetes. ` -func newRootCmd(out io.Writer) *cobra.Command { +func newRootCmd(out io.Writer, in io.Reader) *cobra.Command { cmd := &cobra.Command{ Use: "draft", Short: "The application deployment tool for Kubernetes.", @@ -67,7 +67,7 @@ func newRootCmd(out io.Writer) *cobra.Command { cmd.AddCommand( newCreateCmd(out), newHomeCmd(out), - newInitCmd(out), + newInitCmd(out, in), newUpCmd(out), newVersionCmd(out), ) @@ -84,7 +84,11 @@ func setupConnection(c *cobra.Command, args []string) error { if err != nil { return err } - tunnel, err := portforwarder.New(clientset, config) + clientConfig, err := config.ClientConfig() + if err != nil { + return err + } + tunnel, err := portforwarder.New(clientset, clientConfig) if err != nil { return err } @@ -137,12 +141,13 @@ func homePath() string { // getKubeClient is a convenience method for creating kubernetes config and client // for a given kubeconfig context -func getKubeClient(context string) (*kubernetes.Clientset, *restclient.Config, error) { - config, err := kube.GetConfig(context).ClientConfig() +func getKubeClient(context string) (*kubernetes.Clientset, clientcmd.ClientConfig, error) { + config := kube.GetConfig(context) + clientConfig, err := config.ClientConfig() if err != nil { return nil, nil, fmt.Errorf("could not get kubernetes config for context '%s': %s", context, err) } - client, err := kubernetes.NewForConfig(config) + client, err := kubernetes.NewForConfig(clientConfig) if err != nil { return nil, nil, fmt.Errorf("could not get kubernetes client: %s", err) } @@ -150,7 +155,7 @@ func getKubeClient(context string) (*kubernetes.Clientset, *restclient.Config, e } func main() { - cmd := newRootCmd(os.Stdout) + cmd := newRootCmd(os.Stdout, os.Stdin) if err := cmd.Execute(); err != nil { os.Exit(1) } diff --git a/cmd/draft/init.go b/cmd/draft/init.go index ce14f52..806b958 100644 --- a/cmd/draft/init.go +++ b/cmd/draft/init.go @@ -1,52 +1,62 @@ package main import ( + "bufio" + "encoding/base64" "errors" "fmt" "io" - "io/ioutil" "os" "regexp" + "strings" - "github.com/ghodss/yaml" "github.com/spf13/cobra" - "k8s.io/helm/pkg/chartutil" + "golang.org/x/crypto/ssh/terminal" "k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm/portforwarder" - "k8s.io/helm/pkg/proto/hapi/chart" - "k8s.io/helm/pkg/strvals" "k8s.io/helm/pkg/tiller/environment" + "syscall" + "github.com/Azure/draft/cmd/draft/installer" + installerConfig "github.com/Azure/draft/cmd/draft/installer/config" "github.com/Azure/draft/pkg/draft/draftpath" "github.com/Azure/draft/pkg/draft/pack" ) -const initDesc = ` -This command installs Draftd (the Draft server side component) onto your +const ( + initDesc = ` +This command installs the server side component of Draft onto your Kubernetes Cluster and sets up local configuration in $DRAFT_HOME (default ~/.draft/) To set up just a local environment, use '--client-only'. That will configure -$DRAFT_HOME, but not attempt to connect to a remote cluster and install the Draftd +$DRAFT_HOME, but not attempt to connect to a remote cluster and install the Draft deployment. -To dump information about the Draftd chart, combine the '--dry-run' and '--debug' flags. +To dump information about the Draft chart, combine the '--dry-run' and '--debug' flags. +` + chartConfigTpl = ` +basedomain: %s +registry: + url: %s + org: %s + authtoken: %s ` +) type initCmd struct { - clientOnly bool - upgrade bool - dryRun bool - out io.Writer - home draftpath.Home - helmClient *helm.Client - values []string - rawValueFilePaths []string + clientOnly bool + out io.Writer + in io.Reader + home draftpath.Home + yes bool + helmClient *helm.Client } -func newInitCmd(out io.Writer) *cobra.Command { +func newInitCmd(out io.Writer, in io.Reader) *cobra.Command { i := &initCmd{ out: out, + in: in, } cmd := &cobra.Command{ @@ -63,65 +73,14 @@ func newInitCmd(out io.Writer) *cobra.Command { } f := cmd.Flags() - f.StringArrayVar(&i.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") - f.StringArrayVarP(&i.rawValueFilePaths, "values", "f", []string{}, "specify Draftd values from a values.yaml file (can specify multiple)") - f.BoolVar(&i.upgrade, "upgrade", false, "upgrade if Draftd is already installed") - f.BoolVarP(&i.clientOnly, "client-only", "c", false, "if set does not install Draftd") - f.BoolVar(&i.dryRun, "dry-run", false, "do not install local or remote") + f.BoolVarP(&i.clientOnly, "client-only", "c", false, "install local configuration, but skip remote configuration") + f.BoolVar(&i.yes, "yes", false, "automatically accept configuration defaults (if detected). Exits non-zero if --yes is enabled and no cloud provider was found") return cmd } -func (i *initCmd) vals() ([]byte, error) { - base := map[string]interface{}{} - - // User specified a values files via -f/--values - for _, filePath := range i.rawValueFilePaths { - currentMap := map[string]interface{}{} - bytes, err := ioutil.ReadFile(filePath) - if err != nil { - return []byte{}, err - } - - if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { - return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err) - } - // Merge with the previous map - base = mergeValues(base, currentMap) - } - - // User specified a value via --set - for _, value := range i.values { - if err := strvals.ParseInto(value, base); err != nil { - return []byte{}, fmt.Errorf("failed parsing --set data: %s", err) - } - } - - return yaml.Marshal(base) -} - -// runInit initializes local config and installs Draftd to Kubernetes Cluster +// runInit initializes local config and installs Draft to Kubernetes Cluster func (i *initCmd) run() error { - chartConfig := new(chart.Config) - - rawVals, err := i.vals() - if err != nil { - return err - } - chartConfig.Raw = string(rawVals) - - if flagDebug { - chart, err := chartutil.LoadFiles(installer.DefaultChartFiles) - if err != nil { - return err - } - fmt.Fprintln(i.out, chart) - } - - if i.dryRun { - return nil - } - if err := ensureDirectories(i.home, i.out); err != nil { return err } @@ -131,36 +90,96 @@ func (i *initCmd) run() error { fmt.Fprintf(i.out, "$DRAFT_HOME has been configured at %s.\n", draftHome) if !i.clientOnly { - if i.helmClient == nil { - client, config, err := getKubeClient(kubeContext) + client, clientConfig, err := getKubeClient(kubeContext) + if err != nil { + return fmt.Errorf("Could not get a kube client: %s", err) + } + restClientConfig, err := clientConfig.ClientConfig() + if err != nil { + return fmt.Errorf("Could not retrieve client config from the kube client: %s", err) + } + tunnel, err := portforwarder.New(environment.DefaultTillerNamespace, client, restClientConfig) + if err != nil { + return fmt.Errorf("Could not get a connection to tiller: %s\nPlease ensure you have run `helm init`", err) + } + i.helmClient = helm.NewClient(helm.Host(fmt.Sprintf("localhost:%d", tunnel.Local))) + + chartConfig, cloudProvider, err := installerConfig.FromClientConfig(clientConfig) + if err != nil { + return fmt.Errorf("Could not generate chart config from kube client config: %s", err) + } + + if cloudProvider != "" { + fmt.Fprintf(i.out, "\nDraft detected that you are using %s as your cloud provider. AWESOME!\n", cloudProvider) + fmt.Fprintf(i.out, "Draft will be using the following configuration:\n\n'''\n%s'''\n\n", chartConfig.GetRaw()) + fmt.Fprint(i.out, "Is this okay? [Y/n] ") + reader := bufio.NewReader(i.in) + text, err := reader.ReadString('\n') + if err != nil { + return fmt.Errorf("Could not read input: %s", err) + } + if text == "" || strings.ToLower(text) == "y" { + i.yes = true + } + } + + if !i.yes || cloudProvider == "" { + // prompt for missing information + fmt.Fprintf(i.out, "\nIn order to install Draft, we need a bit more information...\n\n") + fmt.Fprint(i.out, "1. Enter your Docker registry URL (e.g. docker.io, quay.io, myregistry.azurecr.io): ") + reader := bufio.NewReader(i.in) + registryURL, err := reader.ReadString('\n') + if err != nil { + return fmt.Errorf("Could not read input: %s", err) + } + registryURL = strings.TrimSpace(registryURL) + fmt.Fprint(i.out, "2. Enter your username: ") + dockerUser, err := reader.ReadString('\n') + if err != nil { + return fmt.Errorf("Could not read input: %s", err) + } + dockerUser = strings.TrimSpace(dockerUser) + fmt.Fprint(i.out, "3. Enter your password: ") + dockerPass, err := terminal.ReadPassword(syscall.Stdin) if err != nil { - return fmt.Errorf("Could not get a kube client: %s", err) + return fmt.Errorf("Could not read input: %s", err) } - tunnel, err := portforwarder.New(environment.DefaultTillerNamespace, client, config) + fmt.Fprintf(i.out, "\n4. Enter your org where Draft will push images [%s]: ", dockerUser) + dockerOrg, err := reader.ReadString('\n') if err != nil { - return fmt.Errorf("Could not get a connection to tiller: %s", err) + return fmt.Errorf("Could not read input: %s", err) + } + dockerOrg = strings.TrimSpace(dockerOrg) + if dockerOrg == "" { + dockerOrg = dockerUser } - i.helmClient = helm.NewClient(helm.Host(fmt.Sprintf("localhost:%d", tunnel.Local))) + fmt.Fprint(i.out, "5. Enter your top-level domain for ingress (e.g. draft.example.com): ") + basedomain, err := reader.ReadString('\n') + if err != nil { + return fmt.Errorf("Could not read input: %s", err) + } + basedomain = strings.TrimSpace(basedomain) + + registryAuth := base64.StdEncoding.EncodeToString( + []byte(fmt.Sprintf( + `{"username":"%s","password":"%s"}`, + dockerUser, + dockerPass))) + chartConfig.Raw = fmt.Sprintf(chartConfigTpl, basedomain, registryURL, dockerOrg, registryAuth) } if err := installer.Install(i.helmClient, chartConfig); err != nil { - if !IsReleaseAlreadyExists(err) { - return fmt.Errorf("error installing: %s", err) - } - if i.upgrade { - if err := installer.Upgrade(i.helmClient, chartConfig); err != nil { - return fmt.Errorf("error when upgrading: %s", err) - } - fmt.Fprintln(i.out, "\nDraftd (the Draft server side component) has been upgraded to the current version.") + if IsReleaseAlreadyExists(err) { + fmt.Fprintln(i.out, "Warning: Draft is already installed in the cluster.\n"+ + "Use --client-only to suppress this message.") } else { - fmt.Fprintln(i.out, "Warning: Draftd is already installed in the cluster.\n"+ - "(Use --client-only to suppress this message, or --upgrade to upgrade Draftd to the current version.)") + return fmt.Errorf("error installing Draft: %s", err) } } else { - fmt.Fprintln(i.out, "\nDraftd (the Draft server side component) has been installed into your Kubernetes Cluster.") + fmt.Fprintln(i.out, "Draft has been installed into your Kubernetes Cluster.") } } else { - fmt.Fprintln(i.out, "Not installing Draftd due to 'client-only' flag having been set") + fmt.Fprintln(i.out, "Not installing Draft due to 'client-only' flag having been set") } fmt.Fprintln(i.out, "Happy Sailing!") @@ -211,38 +230,6 @@ func ensurePacks(home draftpath.Home, out io.Writer) error { return nil } -// Merges source and destination map, preferring values from the source map -func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} { - for k, v := range src { - // If the key doesn't exist already, then just set the key to that value - if _, exists := dest[k]; !exists { - dest[k] = v - continue - } - nextMap, ok := v.(map[string]interface{}) - // If it isn't another map, overwrite the value - if !ok { - dest[k] = v - continue - } - // If the key doesn't exist already, then just set the key to that value - if _, exists := dest[k]; !exists { - dest[k] = nextMap - continue - } - // Edge case: If the key exists in the destination, but isn't a map - destMap, isMap := dest[k].(map[string]interface{}) - // If the source map has a map for this key, prefer it - if !isMap { - dest[k] = v - continue - } - // If we got to this point, it is a map in both, so merge them - dest[k] = mergeValues(destMap, nextMap) - } - return dest -} - // IsReleaseAlreadyExists returns true if err matches the "release already exists" // error from Helm; else returns false func IsReleaseAlreadyExists(err error) bool { diff --git a/cmd/draft/installer/config/config.go b/cmd/draft/installer/config/config.go new file mode 100644 index 0000000..aadebbd --- /dev/null +++ b/cmd/draft/installer/config/config.go @@ -0,0 +1,29 @@ +package config + +import ( + "k8s.io/client-go/tools/clientcmd" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +// FromClientConfig reads a kubernetes client config, searching for information that may indicate +// this is a minikube/Azure Container Services/Google Container Engine cluster and return +// configuration optimized for that cloud, as well as the cloud provider name. +func FromClientConfig(config clientcmd.ClientConfig) (*chart.Config, string, error) { + var ( + chartConfig = new(chart.Config) + cloudProviderName string + ) + + rawConfig, err := config.RawConfig() + if err != nil { + return nil, "", err + } + + if rawConfig.CurrentContext == "minikube" { + // we imply that the user has installed the registry addon + chartConfig.Raw = "registry:\n url: $(REGISTRY_SERVICE_HOST)\nbasedomain: k8s.local\n" + cloudProviderName = rawConfig.CurrentContext + } + + return chartConfig, cloudProviderName, nil +} diff --git a/cmd/draft/installer/install.go b/cmd/draft/installer/install.go index b1c6f36..57bfe99 100644 --- a/cmd/draft/installer/install.go +++ b/cmd/draft/installer/install.go @@ -36,7 +36,7 @@ service: internalPort: 44135 registry: url: docker.io - org: microsoft + org: draft # This field follows the format of Docker's X-Registry-Auth header. # # See https://github.com/docker/docker/blob/master/docs/api/v1.22.md#push-an-image-on-the-registry @@ -48,7 +48,7 @@ registry: # For token-based logins, use # # $ echo '{"registrytoken":"9cbaf023786cd7"}' | base64 -w 0 - authtoken: changeme + authtoken: e30K ` const draftIgnore = `# Patterns to ignore when building packages. diff --git a/docs/contributing/hacking.md b/docs/contributing/hacking.md index 0f4f5d1..438032a 100644 --- a/docs/contributing/hacking.md +++ b/docs/contributing/hacking.md @@ -7,13 +7,12 @@ development environment for working on the Draft source code. To compile and test Draft binaries and to build Docker images, you will need: + - a [Kubernetes][] cluster with [Draft already installed][install]. We recommend [minikube][]. - [docker][] - - a [Docker Hub][] or [quay.io][quay] account - [git][] - - [Go][] 1.7 or later, with support for compiling to `linux/amd64` + - [helm][], using the same version as recommended in the [installation guide][install]. + - [Go][] 1.8 or later, with support for compiling to `linux/amd64` - [glide][] - - a [Kubernetes][] cluster. We recommend [minikube][] - - [helm][] - [upx][] (optional) to compress binaries for a smaller Docker image In most cases, install the prerequisite according to its instructions. See the next section @@ -84,57 +83,6 @@ To test interactively, you will likely want to deploy your changes to Draft on a This requires a Docker registry where you can push your customized draftd images so Kubernetes can pull them. -In most cases, a local Docker registry will not be accessible to your Kubernetes nodes. A public -registry such as [Docker Hub][] or [Quay][] will suffice. - -To use DockerHub for draftd images: - -```shell -$ export DOCKER_REGISTRY="docker.io" -$ export IMAGE_PREFIX= -``` - -To use quay.io: - -```shell -$ export DOCKER_REGISTRY="quay.io" -$ export IMAGE_PREFIX= -``` - -After your Docker registry is set up, you can deploy your images using: - -```shell -$ make docker-build docker-push -``` - -Ensure that Helm's `tiller` server is running in the Kubernetes cluster: - -```shell -$ helm init # it may take a few seconds for tiller to install -$ helm version -Client: &version.Version{SemVer:"v2.2.0", GitCommit:"fc315ab59850ddd1b9b4959c89ef008fef5cdf89", GitTreeState:"clean"} -Server: &version.Version{SemVer:"v2.2.0", GitCommit:"fc315ab59850ddd1b9b4959c89ef008fef5cdf89", GitTreeState:"clean"} -``` - -To install draftd, edit `chart/values.yaml` and change the fields under `registry` to your -[Docker Hub][] or [quay.io][quay] account, and change the fields under `image` to the newly -deployed draftd image: - -``` -$ $EDITOR chart/values.yaml -``` - -Then, install the chart: - -```shell -$ draft init -f chart/values.yaml -$ helm list # check that Draft has a helm release -NAME REVISION UPDATED STATUS CHART NAMESPACE -draft 1 Thu Feb 16 10:18:21 2017 DEPLOYED draftd-0.1.0 kube-system -``` - -## Re-deploying Your Changes - Because Draft deploys Kubernetes applications and Draft is a Kubernetes application itself, you can use Draft to deploy Draft. How neat is that?! @@ -144,7 +92,7 @@ To build your changes and upload it to draftd, run $ make build docker-binary $ draft up --> Building Dockerfile ---> Pushing docker.io/microsoft/draftd:6f3b53003dcbf43821aea43208fc51455674d00e +--> Pushing 10.0.0.237/draft/draftd:6f3b53003dcbf43821aea43208fc51455674d00e --> Deploying to Kubernetes --> Status: DEPLOYED --> Notes: @@ -176,7 +124,6 @@ helm delete --purge draft [docker]: https://www.docker.com/ -[Docker Hub]: https://hub.docker.com/ [git]: https://git-scm.com/ [glide]: https://github.com/Masterminds/glide [go]: https://golang.org/ @@ -184,6 +131,5 @@ helm delete --purge draft [Homebrew]: https://brew.sh/ [Kubernetes]: https://github.com/kubernetes/kubernetes [minikube]: https://github.com/kubernetes/minikube -[Quay]: https://quay.io/ [upstream]: https://help.github.com/articles/fork-a-repo/ [upx]: https://upx.github.io diff --git a/docs/design.md b/docs/design.md index c317e76..732862f 100644 --- a/docs/design.md +++ b/docs/design.md @@ -193,26 +193,26 @@ image: _How do I add an existing chart to Draft?_ -Just copy (`helm fetch`) it into the `chart/` directory. You need to tweak the values file to -read from `image.registry`, `image.org`, `image.name` and `image.tag` if you want draft to regenerate Docker -images for you. See above. +Just copy (`helm fetch`) it into the `chart/` directory. You need to tweak the values file to read +from `image.registry`, `image.org`, `image.name` and `image.tag` if you want draft to regenerate +Docker images for you. See above. _How do I deploy applications to production?_ Draft is a developer tool. While you _could_ simply use `draft up` to do this, we'd recommend using `helm package` in conjuction with a CI/CD pipeline. -Remember: You can always package a Draft-generated chart with `helm package chart/` and load the -results up to a chart repository, taking advantage of the existing Helm ecosystem. +Remember: You can always package a Draft-generated chart with `helm package` and load the results up +to a chart repository, taking advantage of the existing Helm ecosystem. ## Other Architectural Considerations -Instead of a draftd HTTP server, we could spawn a Draft pod "job" (via `draft up`) that runs only when -`draft up` is called. In that case, the `draft` client would be the main focal point for server-side -configuration. This has the advantage of requiring fewer resource demands server-side, but might -make the client implementation (and security story) significantly more difficult. Furthermore, it -might make two `draft up` operations between two clients differ (the "Works on My Machine!" -problem). +Instead of a draftd HTTP server, we could spawn a Draft pod "job" (via `draft up`) that runs only +when `draft up` is called. In that case, the `draft` client would be the main focal point for +server-side configuration. This has the advantage of requiring fewer resource demands server-side, +but might make the client implementation (and security story) significantly more difficult. +Furthermore, it might make two `draft up` operations between two clients differ (the "Works on My +Machine!" problem). ## User Personas and Stories diff --git a/docs/getting-started.md b/docs/getting-started.md index 2b30e15..8f7053a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -21,8 +21,8 @@ We need some "scaffolding" to deploy our app into a [Kubernetes](https://kuberne $ draft create --> Python app detected --> Ready to sail -$ ls -Dockerfile app.py chart/ draft.toml requirements.txt +$ ls -a +.draftignore Dockerfile app.py chart/ draft.toml requirements.txt ``` The `chart/` and `Dockerfile` assets created by Draft default to a basic Python @@ -145,6 +145,7 @@ The push refers to a repository [docker.io/microsoft/tufted-lamb] Now when we run `curl http://tufted-lamb.example.com`, we can see our app has been updated and deployed to Kubernetes automatically! + [Installation Guide]: install.md [Helm]: https://github.com/kubernetes/helm [Kubernetes]: https://kubernetes.io/ diff --git a/docs/ingress.md b/docs/ingress.md index 79497bd..09dcfd0 100644 --- a/docs/ingress.md +++ b/docs/ingress.md @@ -32,7 +32,7 @@ On minikube, you can simply enable the ingress controller addon $ minikube addon enable ingress ``` -The ingress IP addres is minikube's IP: +The ingress IP address is minikube's IP: ```shell $ minikube ip diff --git a/docs/install.md b/docs/install.md index d9639ba..ed5d3de 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,54 +1,135 @@ +# Install Guide + +Get started with Draft in three easy steps: + +1. Install CLI tools for Helm, Kubectl, [Minikube][] and Draft +2. Boot Minikube and install Draft +3. Deploy your first application + ## Dependencies -- Draft will need a Kubernetes cluster to deploy your app. - [Minikube][minikube], Azure Container Services and Google Container Engine - are a few examples that will work with Draft, but any Kubernetes cluster will do. -- Draft expects [Helm](https://github.com/kubernetes/helm) to be installed on your Kubernetes cluster. Download [`helm`](https://github.com/kubernetes/helm/releases) and -do a `helm init` first, as described in [Installing Helm](https://github.com/kubernetes/helm/blob/master/docs/install.md). -- Draft needs to push images to a Docker registry, so you'll need to configure Draft with your Docker registry credentials. If you don't already have one, you can create a Docker registry for free on either [Docker Hub](https://hub.docker.com/) or [Quay.io](https://quay.io). -- An ingress controller installed within your Kubernetes cluster with a wildcard domain pointing to it. Review the [Ingress Guide][Ingress Guide] for more information about what Draft expects and how to set up an ingress controller. +In order to get started, you will need to fetch the following: -## Install Draft +- [the latest release of minikube](https://github.com/kubernetes/minikube/releases) +- [the latest release of kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) +- [the latest release of Helm](https://github.com/kubernetes/helm/releases) +- [the latest release of Draft](https://github.com/Azure/draft/releases) -Because Draft is currently experimental, there is no stable release out yet and users are expected -to be using the latest build of Draft for testing. Canary releases of the Draft client can be found -at the following links: +Canary releases of the Draft client can be found at the following links: - [Linux amd64](https://azuredraft.blob.core.windows.net/draft/draft-canary-linux-amd64.tar.gz) - [macOS amd64](https://azuredraft.blob.core.windows.net/draft/draft-canary-darwin-amd64.tar.gz) - - Windows amd64 [coming soon!](https://github.com/Azure/draft/issues/61) + - [Windows amd64](https://azuredraft.blob.core.windows.net/draft/draft-canary-darwin-amd64.tar.gz) + +Alternative downloads: + +- [Linux ARM](https://azuredraft.blob.core.windows.net/draft/draft-canary-linux-arm.tar.gz) +- [Linux x86](https://azuredraft.blob.core.windows.net/draft/draft-canary-linux-386.tar.gz) Unpack the Draft binary and add it to your PATH. -## Configure Draft +## Enable Minikube Add-ons + +Now that we have minikube installed, we can go ahead and enable the `registry` and `ingress` +add-ons. + +The ingress add-on is used to allow inbound connections to reach the application. -To install the server-side of Draft, use `draft init` with your ingress' `basedomain` and credentials to let Draft communicate with a Docker registry by using the following command: +The registry add-on is used to store the built docker container within the cluster. + +You can enable the add-ons with ``` -$ draft init --set registry.url=changeme,registry.org=changeme,registry.authtoken=changeme,basedomain=changeme +$ minikube addons enable ingress +$ minikube addons enable registry ``` -* registry.url: Docker Registry Server URL. e.g. Azure Container Registry -> xxxx.azurecr.io, DockerHub -> docker.io -* basedomain: Using a domain that you manage. e.g. `draft.example.com` or use publicly available wildcard dns from [xip.io](https://xip.io). For minikube, as a result, basedomain could be `basedomain=$(minikube ip).xip.io` +## Boot Minikube + +At this point, you can boot up minikube! +``` +$ minikube start +Starting local Kubernetes v1.6.4 cluster... +Starting VM... +oving files into cluster... +Setting up certs... +Starting cluster components... +Connecting to cluster... +Setting up kubeconfig... +Kubectl is now configured to use the cluster. +``` -The auth token field follows the format of Docker's X-Registry-Auth header. -For credential-based logins such as Azure Container Registry, Docker Hub and Quay, use: +Now that the cluster is up and ready, minikube automatically configures kubectl on your machine with +the appropriate authentication and endpoint information. ``` -$ echo '{"username":"jdoe","password":"secret","email":"jdoe@acme.com"}' | base64 +$ kubectl cluster-info +Kubernetes master is running at https://192.168.99.100:8443 + +To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. ``` -For token-based logins such as Google Container Registry and Amazon ECR, use: +## Install Helm + +Once the cluster is ready, you will need to install Helm. Helm is a Kubernetes Package Manager and +is how Draft deploys an application to Kubernetes. + +Installing Helm is quite simple: ``` -$ echo '{"registrytoken":"9cbaf023786cd7"}' | base64 +$ helm init ``` +Wait for Helm to come up and be in a `Ready` state. You can use `kubectl -n kube-system get deploy tiller-deploy --watch` +to wait for tiller to come up. + +## Install Draft + +Now that everything else is set up, we can now install Draft. + +``` +$ draft init +``` + +Follow through the prompts. Draft will read your local kube configuration and notice that it is +pointing at minikube. It will then install Draftd (the Draft server) communicating with the +installed registry add-on, ingress controller and Tiller (Helm server) instances. + +## Configure Ingress Routes + +Draft uses a wildcard domain to make accessing draft-created applications easier. To do so, it +specifies a custom host in the ingress from which tells the backing load balancer to route requests +based on the Host header. + +When Draft was installed on Minikube, a base domain of `k8s.local` was used. To use this domain, you +can edit your `/etc/hosts` file to point to the ingressed out application domain to your cluster. + +The following snippet would allow you to access an application: + +``` +$ sudo echo $(minikube ip) appname.k8s.local >> /etc/hosts +``` + +Unfortunately, `/etc/hosts` does not handle wildcard routes so each application deployed will need +to result in a new route in `/etc/hosts`. Others have worked around this by using other more +sophisticated tools like [dnsmasq][]. + +To use wildcard domains with dnsmasq, add a new rule in `dnsmasq.conf`: + +``` +$ sudo echo "address=/k8s.local/$(minikube ip)" >> dnsmasq.conf +``` + +See the [Ingress Guide][] for a more detailed setup. + ## Take Draft for a Spin -Once you've completed the above steps, you're ready to climb aboard and explore the [Getting Started Guide][Getting Started] - you'll soon be sailing! +Once you've completed the above steps, you're ready to climb aboard and explore the +[Getting Started Guide][Getting Started] - you'll soon be sailing! -[Ingress Guide]: ingress.md + +[dnsmasq]: https://wiki.archlinux.org/index.php/dnsmasq [Getting Started]: getting-started.md +[Ingress Guide]: ingress.md [minikube]: https://github.com/kubernetes/minikube diff --git a/docs/packs.md b/docs/packs.md index 77041b5..5e8a236 100644 --- a/docs/packs.md +++ b/docs/packs.md @@ -16,9 +16,9 @@ starter packs. ## The Starter Pack Structure -A starter pack is organized inside a directory in `$(draft home)/packs`. Inside the pack's directory, -there will be a template `chart/` and a `Dockerfile` that will be injected into the application -when the starter pack is requested. +A starter pack is organized inside a directory in `$(draft home)/packs`. Inside the pack's +directory, there will be a template `chart/` and a `Dockerfile` that will be injected into the +application when the starter pack is requested. Inside this directory, Draft will expect a structure like this: @@ -60,10 +60,10 @@ See [Helm's documentation on Charts][charts] for more information on the Chart f ## Pack Detection -When `draft create` is executed on an application, Draft starts iterating through the packs available -in `$(draft home)/packs`. Each pack optionally has an executable file named `detect` in the root -directory. The intention of this `detect` script is to determine if the pack should be used with -the given application. +When `draft create` is executed on an application, Draft starts iterating through the packs +available in `$(draft home)/packs`. Each pack optionally has an executable file named `detect` in +the root directory. The intention of this `detect` script is to determine if the pack should be used +with the given application. A detect executable takes one argument: the directory in which `draft create` was executed. The executable should be portable across most systems (read: not a Ruby/Python script unless it's @@ -90,4 +90,5 @@ If a pack does not include a detect executable, it is considered a "loser". Pack overridden with the `--pack` flag. The detect script will not be considered and Draft will bootstrap the app with the pack, no questions asked. + [charts]: https://github.com/kubernetes/helm/blob/master/docs/charts.md diff --git a/docs/plugins.md b/docs/plugins.md index 5cad130..bc365a8 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -5,9 +5,9 @@ built-in Draft codebase. This guide explains how to use and create plugins. ## An Overview -Draft plugins are add-on tools that integrate seamlessly with Draft. They provide a way to extend the -core feature set of Draft, but without requiring every new feature to be written in Go and added to -the core tool. +Draft plugins are add-on tools that integrate seamlessly with Draft. They provide a way to extend +the core feature set of Draft, but without requiring every new feature to be written in Go and added +to the core tool. Draft plugins have the following features: diff --git a/docs/user-guide.md b/docs/user-guide.md index 57d1427..8552206 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -37,9 +37,9 @@ that in a second. ``` This is the environment name. Applications deployed by Draft can be configured in different manners -based on the present environment. By default, `draft up` deploys using the `development` environment, -but this can be tweaked by either setting `$DRAFT_ENV` or by supplying the environment name at -runtime using `draft up --environment=staging`. +based on the present environment. By default, `draft up` deploys using the `development` +environment, but this can be tweaked by either setting `$DRAFT_ENV` or by supplying the environment +name at runtime using `draft up --environment=staging`. ``` name = "draft" @@ -63,7 +63,9 @@ Here is a run-down on each of the fields: - `watch`: whether or not to deploy the app automatically when local files change. - `watch_delay`: the delay for local file changes to have stopped before deploying again (in seconds). -Note: All updates to the `draft.toml` will take effect the next time `draft up --environment=` is invoked _except_ the `namespace` key/value pair. Once a deployment has occurred in the original namespace, it won't be transferred over to another. +Note: All updates to the `draft.toml` will take effect the next time +`draft up --environment=` is invoked _except_ the `namespace` key/value pair. +Once a deployment has occurred in the original namespace, it won't be transferred over to another. [toml]: https://github.com/toml-lang/toml