| Index: deploytool/README.md
|
| diff --git a/deploytool/README.md b/deploytool/README.md
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..82aa4dd71b4cdc5d18edf6d23a5106eabac76b21
|
| --- /dev/null
|
| +++ b/deploytool/README.md
|
| @@ -0,0 +1,478 @@
|
| +The LUCI cloud services deployment tool
|
| +=======================================
|
| +
|
| +The LUCI project hosts code on Google AppEngine, Google Container Engine (via
|
| +[Kubernetes](http://kubernetes.io/)), and several other locations. The code
|
| +layout is complex and modular, which is oftentimes at odds with the layout
|
| +requirements of supported deployment tools such as `gcloud` and `docker`. The
|
| +`luci_deploy` attempts to smooth that over by providing:
|
| +
|
| +* A common declarative deployment configuration language.
|
| +* Hermetic, pinned source checkouts.
|
| +* A single tool capable of organizing and executing deployment programs.
|
| +* A utility that facilitates deployment management given its understanding of
|
| + the deployment parameters.
|
| +
|
| +While much of the underlying code in the deployment tool is cross-platform, it
|
| +is specifically targeting Linux support. Any deployment from other platforms is,
|
| +at this time, unsupported.
|
| +
|
| +The deployment tool doesn't reimplement all of the functionality of existing
|
| +tools; instead, it manages configurations and layouts and employs existing
|
| +tooling to perform the actual deployment operations. Some tools that it uses
|
| +are:
|
| +* [gcloud](https://cloud.google.com/sdk/gcloud/), the Google Cloud SDK tool.
|
| +* [kubectl](http://kubernetes.io/docs/user-guide/kubectl-overview/), the
|
| + Kubernetes control tool.
|
| +* [aedeploy](https://godoc.org/google.golang.org/appengine/cmd/aedeploy),
|
| + a tool which collects `GOPATH` packages for Docker deployment.
|
| +* [docker](https://www.docker.com/), a container management system used by
|
| + Kubernetes.
|
| +
|
| +Deployment configuration for a given deployable component is stored in two
|
| +places:
|
| +
|
| +1. Generic component configuration and requirements are stored alongside the
|
| + component in its source repository.
|
| +1. Deployment-specific parameters are stored in a separate repository, and
|
| + project the generic component layout into a product space.
|
| +
|
| +The component-specific configuration details which resources the component
|
| +needs, which services it employs, and its relationship with other components.
|
| +The deployment projection then takes that generic configuration and applies it
|
| +to a set of specific deployment parameters. For example:
|
| +
|
| +* One deployment may project a component into a production environment,
|
| + allocating expensive CPU resources.
|
| +* A development deployment may project the same component into a development
|
| + environment.
|
| +* A staging deployment may project the same component into a staging
|
| + enviornment alongside other components from other projects.
|
| +
|
| +
|
| +## Overview and Terminology
|
| +
|
| +Deployment configuration uses the following concepts:
|
| +
|
| +* A **source** is a specific named repository checked out at a specific
|
| + revision.
|
| +* A **source group** is a collection of **source** repositories.
|
| +* A **component** is a single buildable and deployable entity. Its configuration
|
| + resides in a specific **source**.
|
| +* An **application** is a collection of deployable **component** pointers. Each
|
| + pointer is a subpath to the **component** configuration within its **source**.
|
| +* A **resource** is a global cloud resource (e.g., cloud project, Kubernetes
|
| + cluster, etc.).
|
| +* A **deployment** binds an **application** to a **source group** (and,
|
| + consequently, a specific set of **sources**) and a set of **resources**.
|
| +
|
| +During deployment, several operations are performed:
|
| +
|
| +* The **working directory** is used to contain files generated and managed by
|
| + the `luci_deploy`.
|
| +* The **checkout** is a read-only directory containing all of the **sources**
|
| + checked out at their specific revisions and initialized.
|
| +* The **staging** directory is a constructed environment containing deployment
|
| + and component sources and generated files organized such that deployment tools
|
| + can operate on them. See [Staging](#staging) for more information.
|
| +
|
| +A user will first perform a `checkout`, which loads all of the configured
|
| +**sources** at their specified revisions into the working directory. Afterwards,
|
| +the user performs operations on the checkout:
|
| +* `deploy`, which deploy the configured **deployments** and their
|
| + composite **components**.
|
| +* `manage`, which offers component management utilities based on the
|
| + deployment configuration.
|
| +
|
| +
|
| +## Deployment Layout
|
| +
|
| +The deployment layout is a filesystem-based configuration structure that
|
| +defines the parameters of the configured deployments. Unless stated otherwise,
|
| +all layout component names may only contain:
|
| +- Alphanumeric characters (`[0-9a-zA-Z]`)
|
| +- A hyphen (`-`).
|
| +
|
| +Configuration files are text-encoded protobufs. All protobufs used by the
|
| +`luci_deploy` are defined in its [api directory](api/deploy). Text protobuf
|
| +configuration files have the ".cfg" suffix. The protobuf messages used in the
|
| +layout are defined in [config.proto](api/deploy/config.proto).
|
| +
|
| +The default deployment layout consists of a root `layout.cfg` file and several
|
| +subdirectories defining **source groups**, **applications**, and
|
| +**deployments**. Note that the layout depicted below is the *default* layout;
|
| +the specific config directory paths may be overridden in `layout.cfg`.
|
| +
|
| +```
|
| +/layout.cfg
|
| +/sources/
|
| + <source-group-name>/ (Defines a source group).
|
| + <source-name>.cfg (Defines a source within the source group).
|
| +
|
| +/applications/
|
| + <application-name>.cfg (Defines a application).
|
| +
|
| +/deployments/
|
| + <deployment-name>.cfg (Defines a deployment).
|
| +```
|
| +
|
| +All configuration files
|
| +* `layout.cfg`, a text protobuf file containing a `Layout` message.
|
| +* `<source-group-name>`, which defines a source group.
|
| +* `<source-name>.cfg`, which defines a `Source` message within its
|
| + **source group** parent directory.
|
| +* `<application-name>.cfg`, which defines an `Application` message.
|
| +* `<deployment-name>.cfg`, which defines a `Deployment` message.
|
| +
|
| +## Sources
|
| +
|
| +**Sources** define a fully-specified repository in which **component**
|
| +configuration and data are stored. `luci_deploy` will manage the source
|
| +checkouts configured in the deployment layout.
|
| +
|
| +A **source** is a file located within a source group directory named
|
| +`<source-name>.cfg`. The source is specified using the `Source` text protobuf,
|
| +defined in [config.proto](api/deploy/config.proto). Each **source** name is
|
| +unique within its **source group**, but **source** names can (and likely will)
|
| +be re-used across other **source groups** to enable deployment tracks (see
|
| +[Source Group Versioning](#source-group-versioning)).
|
| +
|
| +Each **source** checkout is read-only, meaning that after the `checkout`
|
| +operation is complete, no other deployment operations will modify its contents.
|
| +
|
| +### Source Initialization
|
| +
|
| +**Sources** may require additional post-checkout steps to render them usable.
|
| +Such sources can offer initialization scripts that will be run by `luci_deploy`
|
| +during the `checkout` phase after the source has been checked out.
|
| +
|
| +Initialization scripts are specified by adding a `SourceLayout` text protobuf
|
| +file called `luci-deploy.cfg`, to the root of the **source**. If present,
|
| +this file will be interpreted, and any initialization options present will be
|
| +included as part of the **source**'s checkout operation.
|
| +
|
| +The `SourceLayout` protobuf is defined in
|
| +[checkout.proto](api/deploy/checkout.proto).
|
| +
|
| +Note that the **source**'s definition *must* have `run_scripts` set to true in
|
| +order for initialization scripts to be executed. This is a security precaution
|
| +to prevent untrusted **source** repositories from adding and executing
|
| +`luci_deploy` commands without the user's permission.
|
| +
|
| +### Source Group Versioning
|
| +
|
| +**Applications** bind components to source *name*. Deployments then bind those
|
| +**applications** to **source groups**. Therefore, the specific **source** that
|
| +is used in a deployment is determined by the **deployment**'s
|
| +choice of **source group**.
|
| +
|
| +An example is a directory layout with two source groups, one named `canary` and
|
| +one named `production`.
|
| +
|
| +```
|
| +sources/canary/base.cfg
|
| +sources/production/base.cfg
|
| +```
|
| +
|
| +The source named `base` is defined in both **source groups** is then used in
|
| +an **application**. At this point, the **application's** specific **source**
|
| +is not known, since the choice of *which* `base` to use requires a **source
|
| +group** to be selected. In other words, the **application** is bound to
|
| +`sources/*/base.cfg`, and the choice of which `*` to use is left to a specific
|
| +**deployment**.
|
| +
|
| +A *canary* deployment can bind the **application** to the `canary` **source
|
| +group**, causing its **components** to be loaded from the version of `base`
|
| +defined in `sources/canary/base.cfg`. A *production* deployment can bind the
|
| +same **application** to the `production` **source group**, causing its
|
| +**components** to be loaded from `sources/production/base.cfg`.
|
| +
|
| +### Source-Relative Paths
|
| +
|
| +Paths referenced within a source are referenced using "source-relative paths".
|
| +These paths are:
|
| +* Operating system independent, as they all use "/" as their delimiter.
|
| +* Either **absolute** (starting with a "/") or **relative** (not starting with
|
| + a "/"). Relative paths are relative to the configuration file in which they
|
| + are specified.
|
| +
|
| +## Checkout
|
| +
|
| +Prior to deployment, a user must sync the checked out sources with the
|
| +configured sources using the `checkout` sub-command. This checks out *all*
|
| +sources defined in the deployment layout and runs any configured initialization
|
| +scripts to initialize them.
|
| +
|
| +A `checkout` is a manual operation. If the configured **sources** change, a new
|
| +`checkout` operation must be performed to sync the on-disk checkout with the
|
| +configured sources.
|
| +
|
| +Performing a checkout is simple:
|
| +
|
| +```shell
|
| +$ luci_deploy checkout
|
| +```
|
| +
|
| +### Local Checkout
|
| +
|
| +It is oftentimes useful for a user to stage (for testing) or deploy (for triage)
|
| +code from their local checkout. Rather than editing the **source** files to use
|
| +`file:///` URLs, the user may define a set of repository overrides in their
|
| +[User Configuration](#user-configuration). Running the `checkout` sub-command
|
| +with the `--local` flag will cause the checkout to prefer the user configuration
|
| +repositroies to those configured in the **sources**.
|
| +
|
| +```shell
|
| +$ luci_deploy checkout --local
|
| +```
|
| +
|
| +**NOTE**: The checkout will continue to use the local overrides until the
|
| +`checkout` sub-command is re-run without the `--local` flag. However, also note
|
| +that a checkout containing a local override is considered *tainted*.
|
| +
|
| +## Deployment
|
| +
|
| +Deployment is accessed using the `deploy` sub-command. A user may deploy a
|
| +single component or a full deployment. All deployment operations are run in
|
| +stages, and, within each stage, in parallel.
|
| +
|
| +```shell
|
| +# Deploy all Components within a Deployment.
|
| +$ luci_deploy deploy mydeployment
|
| +
|
| +# Deploy a specific set of Components.
|
| +$ luci_deploy deploy mydeployment/component-a mydeployment/component-b
|
| +```
|
| +
|
| +The `deploy` operation consists of several sub-stages:
|
| +1. `stage`, where deployable **components** are generated from their sources and
|
| + configurations. See [Staging](#staging) for more information.
|
| +1. `localbuild`, where any local build operations are performed on the staged
|
| + **components**. For some component types, this doubles as a sanity check prior
|
| + to engaging remote services.
|
| +1. `push`, where **components** are uploaded to their remote platforms.
|
| +1. `commit`, where the uploaded **components** are activated and become live.
|
| +
|
| +For testing and debugging purposes, a deployment can be stopped prior to a full
|
| +commit by using the `--stage` parameter. For example, to assert that all
|
| +**components** in a deployment can be successfully staged and locally built, a
|
| +user may run:
|
| +
|
| +```shell
|
| +$ luci_deploy deploy --stage=localbuild mydeployment
|
| +```
|
| +
|
| +## Component Configuration
|
| +
|
| +A **component** is a single buildable/deployable unit. **Components** are
|
| +defined in **application** configuration files, and are specified as paths to
|
| +`Component` messages within a **source**. `Component` messages are defined in
|
| +[component.proto](api/deploy/component.proto).
|
| +
|
| +**Components** are intentionally defined within a **source**, as opposed to the
|
| +deployment layout, because their specific composition and resource requirements
|
| +will be versioned alongside their deployable code. For example, different
|
| +versions of a Google AppEngine app may define different datastore indexes based
|
| +on the underlying functionality of their code.
|
| +
|
| +## User Configuration
|
| +
|
| +The user may include a configuration file in their home directory at
|
| +`~/.luci_deploy.cfg`. If present, this file will be loaded alongside the
|
| +layout configuration and incorporated into `luci_deploy` behavior.
|
| +
|
| +The user configuration file is a `UserConfig` text protobuf defined in
|
| +[userconfig.proto](api/deploy/userconfig.proto).
|
| +
|
| +## Staging
|
| +
|
| +A staging space is created for each deployable Component. Staging offers
|
| +`luci_deploy` an isolated canvas with which it can the filesystem layouts for
|
| +that Component's actual deployment tooling to operate. All file operations,
|
| +generation, and structuring are performed in the staging state such that the
|
| +resulting staging directory is available for tooling or humans to use.
|
| +
|
| +The staging space for a given Component depends on that Component's type.
|
| +A staging layout is intended to be human-navigatable while conforming to any
|
| +layout requirements imposed by that Component's deployment tooling.
|
| +
|
| +One goal that is enforced is that the actual checkout directories used by
|
| +any given Component are considered read-only. This means that staging for
|
| +Components which require generated files to exist alongside source must
|
| +copy or mirror that source elsewhere. Some of the more convoluted aspects of
|
| +staging layouts are the result of this requirement.
|
| +
|
| +### AppEngine
|
| +
|
| +AppEngine deployments are composed of two sets of information:
|
| +* Individual AppEngine modules, including the default module.
|
| +* AppEngine Project-wide globals such as Index, Cron, Dispatch, and Queue
|
| + settings.
|
| +
|
| +The individual Components are staged and deployed independently. The globals
|
| +are composed of their respective settings in each individual Component
|
| +associated with the AppEngine project regardless of whether that Component is
|
| +actually being deployed. The aggregate globals are re-asserted once per
|
| +cloud project at the end of module deployment.
|
| +
|
| +References to static content are flattened into static directories within the
|
| +staging area. The generated YAML files are configured to point to this
|
| +flattened space regardless of the original static content's location within
|
| +the source.
|
| +
|
| +#### dev_appserver
|
| +
|
| +Moving Component configuration into protobufs and constructing composite
|
| +GOPATH and generated configuration files prevents AppEngine tooling from
|
| +working out of the box in the source repository.
|
| +
|
| +The offered solution is to manually stage the Components under test, then
|
| +run tooling against the staged Component directories. The user may optionally
|
| +install the staged paths (GOPATH, etc.) or use their default environment's
|
| +paths.
|
| +
|
| +One downside to this solution is that staging operates on a snapshot of the
|
| +repository, meaning that changes to the repository won't be reflected in the
|
| +staged environment. The user can address this by either manually re-staging
|
| +the Deployment when a file changes. The user may also structure their
|
| +application such that the staged content doesn't change frequently (e.g.,
|
| +for Go, have a simple entry point that immediately imports the main app
|
| +logic). Specific structures depend on the type of Component and how it is
|
| +staged (see below).
|
| +
|
| +In the future, a command to bootstrap "dev_appserver" through the staged
|
| +environment would be useful.
|
| +
|
| +#### Go on Classic AppEngine
|
| +
|
| +Go Classic AppEngine Components are deployed using the `appcfg.py` tool,
|
| +which is the fastest available method to deploy such applications.
|
| +
|
| +Because the "app.yaml" file must exist alongside the deployed source, a
|
| +stub entry point is generated in the staging area. This stub simply imports
|
| +the actual entry point package.
|
| +
|
| +The Component's Sources which declare GOPATH presence are combined in a
|
| +virutal GOPATH within the Component's staging area. This GOPATH is then
|
| +installed and `appcfg.py`'s "update" method is invoked to upload the
|
| +Component.
|
| +
|
| +#### Go on Managed VM
|
| +
|
| +Go AppEngine Managed VMs use a set of tools for deployment:
|
| +* `aedeploy`, an AppEngine project tool which copies the various referenced
|
| + sources across GOPATH entries into a single GOPATH hierarchy for Docker
|
| + isolation.
|
| +* `gcloud`, which engages the remote AppEngine service and offers deployment
|
| + utility.
|
| +* Behind the scenes, `gcloud` uses `docker` to build the actual deployed
|
| + image from the source (`aedeploy` target) and assembled GOPATH.
|
| +
|
| +Because the "app.yaml" file must exist alongside the entry point code, the
|
| +contents of the entry package are copied (via symlink) into a generated
|
| +entry point package in the staging area.
|
| +
|
| +The Component's Sources which declare GOPATH presence are combined in a
|
| +virutal GOPATH within the Component's staging area. This GOPATH is then
|
| +collapsed by `aedeploy` when the Managed VM image is built.
|
| +
|
| + NOTE: because the entry point is actually a clone of the entry point
|
| + package, "internal/" imports will not work. This can be easily worked around
|
| + by having the entry point import another non-internal package within the
|
| + project, and having that package act as the actual entry point.
|
| +
|
| +#### Static Content Module
|
| +
|
| +The `luci_deploy` supports the concept of a static content module. This is an
|
| +AppEngine module whose sole purpose is to, via AppEngine handler definitions,
|
| +map to uploaded static content. The utility of a static module is that it can
|
| +be effortlessly updated without impacting actual AppEngine runtime processes.
|
| +Static modules can be used in conjunction with module-referencing handlers in
|
| +the default AppEngine module to create the effect of the default module
|
| +actually hosting the static content.
|
| +
|
| +Static content modules are staged like other AppEngine modules, only with
|
| +no running code.
|
| +
|
| +### Container Engine / Kubernetes
|
| +
|
| +The `luci_deploy` supports depoying services to Google Container Engine, which
|
| +is backed by Kubernetes. A project's Container Engine configuration consists
|
| +of a series of Container Engine clusters, each of which hosts a series of
|
| +homogenous machines. Kubernetes Pods, each of which are composed of one or
|
| +more Kubernetes Components (i.e., Docker images), are deployed to one or more
|
| +Google Container Engine Clusters.
|
| +
|
| +The Deployment Component defines a series of Container Engine Pods, an
|
| +amalgam of a Kubernetes Pod definition and Container Engine requirements of
|
| +that Kubernetes Pod. Each Kubernetes Pod's Component is built in its own
|
| +staging area and deployed as part of that Pod to one or more Container Engine
|
| +clusters.
|
| +
|
| +The Build phase of Container Engine deployment constructs a local Docker
|
| +image of each Kubernetes Component. The Push phase pushes those images to
|
| +the remote Docker image service. The Commit phase enacts the generated
|
| +Kubernetes/ configuration which references those images on the Container
|
| +Engine configuration.
|
| +
|
| +Container Engine management is done in two layers: firstly, the Container
|
| +Engine configuration is managed with `gcloud` commands. This configures:
|
| +
|
| +* Which managed Clusters exist on Google Container Engine.
|
| +* What scopes, system specs, and node count each Cluster has.
|
| +
|
| +Within a Cluster, several managed Kubernetes pods are deployed. The
|
| +deployment is done using the `kubectl` tool, selecting the cluster using
|
| +the "--context" flag to select the `gcloud`-generated Kubernetes context.
|
| +
|
| +Each `luci_deploy` Component (Kubernetes Pod, at this level) is managed as a
|
| +Kubernetes Deployment (http://kubernetes.io/docs/user-guide/deployments/). A
|
| +`luci_deploy`-managed Deployment will have the following metadata annotations:
|
| +
|
| +* "luci.managedBy", set to "luci-deploytool"
|
| +* "luci.deploytool/version" set to the `deploytool` Deployment's version
|
| + string.
|
| +* "luci.deploytool/sourceVersion" set to the revision of the Deployment
|
| + Component's source.
|
| +
|
| +The Kubernetes Deployment for a Component will be named
|
| +"<project>--<component>".
|
| +
|
| +`luci_deploy`-driven Kubernetes depoyment is fairly straightforward:
|
| +* Use "kubectl get deployments/<name>" to get the current Deployment state.
|
| +* If there is a current Deployment,
|
| + * If its "luci.managedBy" annotation doesn't equal "luci-deploytool",
|
| + fail. The user must manually correct this situation.
|
| + * Check if the "luci.deploytool/version" matches the container version.
|
| + If it does, succeed.
|
| +* Create/update the Deployment's configuration using "kubectl apply".
|
| +
|
| +#### Go Container Engine Pod Components
|
| +
|
| +Go Container Engine Pods use a set of tools for deployment:
|
| +* `aedeploy`, an AppEngine project tool which copies the various referenced
|
| + sources across GOPATH entries into a single GOPATH hierarchy for Docker
|
| + isolation.
|
| +* `gcloud`, which engages the remote AppEngine service and offers deployment
|
| + utility.
|
| +* `docker`, which is used to build and manage the Docker images.
|
| +
|
| +The Component's Sources which declare GOPATH presence are combined in a
|
| +virutal GOPATH within the Component's staging area. This GOPATH is then
|
| +collapsed by `aedeploy` when the Docker image is built.
|
| +
|
| +The actual Component is built directly from the Source using `aedeploy` and
|
| +`docker build`. This is acceptable, since this is a read-only operation and
|
| +will not modify the Source.
|
| +
|
| +## To Do
|
| +
|
| +Following are some ideas of "planned" features that would be useful to add
|
| +to `luci_deploy`:
|
| +* Add a "manage" command to manage a specific Deployment and/or Component.
|
| + Each invocation would load special set of subcommands based on that
|
| + resource:
|
| + * If the resource is a Deployment, query status?
|
| + * Other common automatable management macros.
|
| +* Offer a `dev_appserver` fallthrough to create a staging area and
|
| + bootstrap `dev_appserver` for the named staged AppEngine Components.
|
|
|