| OLD | NEW |
| (Empty) | |
| 1 The LUCI cloud services deployment tool |
| 2 ======================================= |
| 3 |
| 4 The LUCI project hosts code on Google AppEngine, Google Container Engine (via |
| 5 [Kubernetes](http://kubernetes.io/)), and several other locations. The code |
| 6 layout is complex and modular, which is oftentimes at odds with the layout |
| 7 requirements of supported deployment tools such as `gcloud` and `docker`. The |
| 8 `luci_deploy` attempts to smooth that over by providing: |
| 9 |
| 10 * A common declarative deployment configuration language. |
| 11 * Hermetic, pinned source checkouts. |
| 12 * A single tool capable of organizing and executing deployment programs. |
| 13 * A utility that facilitates deployment management given its understanding of |
| 14 the deployment parameters. |
| 15 |
| 16 While much of the underlying code in the deployment tool is cross-platform, it |
| 17 is specifically targeting Linux support. Any deployment from other platforms is, |
| 18 at this time, unsupported. |
| 19 |
| 20 The deployment tool doesn't reimplement all of the functionality of existing |
| 21 tools; instead, it manages configurations and layouts and employs existing |
| 22 tooling to perform the actual deployment operations. Some tools that it uses |
| 23 are: |
| 24 * [gcloud](https://cloud.google.com/sdk/gcloud/), the Google Cloud SDK tool. |
| 25 * [kubectl](http://kubernetes.io/docs/user-guide/kubectl-overview/), the |
| 26 Kubernetes control tool. |
| 27 * [aedeploy](https://godoc.org/google.golang.org/appengine/cmd/aedeploy), |
| 28 a tool which collects `GOPATH` packages for Docker deployment. |
| 29 * [docker](https://www.docker.com/), a container management system used by |
| 30 Kubernetes. |
| 31 |
| 32 Deployment configuration for a given deployable component is stored in two |
| 33 places: |
| 34 |
| 35 1. Generic component configuration and requirements are stored alongside the |
| 36 component in its source repository. |
| 37 1. Deployment-specific parameters are stored in a separate repository, and |
| 38 project the generic component layout into a product space. |
| 39 |
| 40 The component-specific configuration details which resources the component |
| 41 needs, which services it employs, and its relationship with other components. |
| 42 The deployment projection then takes that generic configuration and applies it |
| 43 to a set of specific deployment parameters. For example: |
| 44 |
| 45 * One deployment may project a component into a production environment, |
| 46 allocating expensive CPU resources. |
| 47 * A development deployment may project the same component into a development |
| 48 environment. |
| 49 * A staging deployment may project the same component into a staging |
| 50 enviornment alongside other components from other projects. |
| 51 |
| 52 |
| 53 ## Overview and Terminology |
| 54 |
| 55 Deployment configuration uses the following concepts: |
| 56 |
| 57 * A **source** is a specific named repository checked out at a specific |
| 58 revision. |
| 59 * A **source group** is a collection of **source** repositories. |
| 60 * A **component** is a single buildable and deployable entity. Its configuration |
| 61 resides in a specific **source**. |
| 62 * An **application** is a collection of deployable **component** pointers. Each |
| 63 pointer is a subpath to the **component** configuration within its **source**. |
| 64 * A **resource** is a global cloud resource (e.g., cloud project, Kubernetes |
| 65 cluster, etc.). |
| 66 * A **deployment** binds an **application** to a **source group** (and, |
| 67 consequently, a specific set of **sources**) and a set of **resources**. |
| 68 |
| 69 During deployment, several operations are performed: |
| 70 |
| 71 * The **working directory** is used to contain files generated and managed by |
| 72 the `luci_deploy`. |
| 73 * The **checkout** is a read-only directory containing all of the **sources** |
| 74 checked out at their specific revisions and initialized. |
| 75 * The **staging** directory is a constructed environment containing deployment |
| 76 and component sources and generated files organized such that deployment tools |
| 77 can operate on them. See [Staging](#staging) for more information. |
| 78 |
| 79 A user will first perform a `checkout`, which loads all of the configured |
| 80 **sources** at their specified revisions into the working directory. Afterwards, |
| 81 the user performs operations on the checkout: |
| 82 * `deploy`, which deploy the configured **deployments** and their |
| 83 composite **components**. |
| 84 * `manage`, which offers component management utilities based on the |
| 85 deployment configuration. |
| 86 |
| 87 |
| 88 ## Deployment Layout |
| 89 |
| 90 The deployment layout is a filesystem-based configuration structure that |
| 91 defines the parameters of the configured deployments. Unless stated otherwise, |
| 92 all layout component names may only contain: |
| 93 - Alphanumeric characters (`[0-9a-zA-Z]`) |
| 94 - A hyphen (`-`). |
| 95 |
| 96 Configuration files are text-encoded protobufs. All protobufs used by the |
| 97 `luci_deploy` are defined in its [api directory](api/deploy). Text protobuf |
| 98 configuration files have the ".cfg" suffix. The protobuf messages used in the |
| 99 layout are defined in [config.proto](api/deploy/config.proto). |
| 100 |
| 101 The default deployment layout consists of a root `layout.cfg` file and several |
| 102 subdirectories defining **source groups**, **applications**, and |
| 103 **deployments**. Note that the layout depicted below is the *default* layout; |
| 104 the specific config directory paths may be overridden in `layout.cfg`. |
| 105 |
| 106 ``` |
| 107 /layout.cfg |
| 108 /sources/ |
| 109 <source-group-name>/ (Defines a source group). |
| 110 <source-name>.cfg (Defines a source within the source group). |
| 111 |
| 112 /applications/ |
| 113 <application-name>.cfg (Defines a application). |
| 114 |
| 115 /deployments/ |
| 116 <deployment-name>.cfg (Defines a deployment). |
| 117 ``` |
| 118 |
| 119 All configuration files |
| 120 * `layout.cfg`, a text protobuf file containing a `Layout` message. |
| 121 * `<source-group-name>`, which defines a source group. |
| 122 * `<source-name>.cfg`, which defines a `Source` message within its |
| 123 **source group** parent directory. |
| 124 * `<application-name>.cfg`, which defines an `Application` message. |
| 125 * `<deployment-name>.cfg`, which defines a `Deployment` message. |
| 126 |
| 127 ## Sources |
| 128 |
| 129 **Sources** define a fully-specified repository in which **component** |
| 130 configuration and data are stored. `luci_deploy` will manage the source |
| 131 checkouts configured in the deployment layout. |
| 132 |
| 133 A **source** is a file located within a source group directory named |
| 134 `<source-name>.cfg`. The source is specified using the `Source` text protobuf, |
| 135 defined in [config.proto](api/deploy/config.proto). Each **source** name is |
| 136 unique within its **source group**, but **source** names can (and likely will) |
| 137 be re-used across other **source groups** to enable deployment tracks (see |
| 138 [Source Group Versioning](#source-group-versioning)). |
| 139 |
| 140 Each **source** checkout is read-only, meaning that after the `checkout` |
| 141 operation is complete, no other deployment operations will modify its contents. |
| 142 |
| 143 ### Source Initialization |
| 144 |
| 145 **Sources** may require additional post-checkout steps to render them usable. |
| 146 Such sources can offer initialization scripts that will be run by `luci_deploy` |
| 147 during the `checkout` phase after the source has been checked out. |
| 148 |
| 149 Initialization scripts are specified by adding a `SourceLayout` text protobuf |
| 150 file called `luci-deploy.cfg`, to the root of the **source**. If present, |
| 151 this file will be interpreted, and any initialization options present will be |
| 152 included as part of the **source**'s checkout operation. |
| 153 |
| 154 The `SourceLayout` protobuf is defined in |
| 155 [checkout.proto](api/deploy/checkout.proto). |
| 156 |
| 157 Note that the **source**'s definition *must* have `run_scripts` set to true in |
| 158 order for initialization scripts to be executed. This is a security precaution |
| 159 to prevent untrusted **source** repositories from adding and executing |
| 160 `luci_deploy` commands without the user's permission. |
| 161 |
| 162 ### Source Group Versioning |
| 163 |
| 164 **Applications** bind components to source *name*. Deployments then bind those |
| 165 **applications** to **source groups**. Therefore, the specific **source** that |
| 166 is used in a deployment is determined by the **deployment**'s |
| 167 choice of **source group**. |
| 168 |
| 169 An example is a directory layout with two source groups, one named `canary` and |
| 170 one named `production`. |
| 171 |
| 172 ``` |
| 173 sources/canary/base.cfg |
| 174 sources/production/base.cfg |
| 175 ``` |
| 176 |
| 177 The source named `base` is defined in both **source groups** is then used in |
| 178 an **application**. At this point, the **application's** specific **source** |
| 179 is not known, since the choice of *which* `base` to use requires a **source |
| 180 group** to be selected. In other words, the **application** is bound to |
| 181 `sources/*/base.cfg`, and the choice of which `*` to use is left to a specific |
| 182 **deployment**. |
| 183 |
| 184 A *canary* deployment can bind the **application** to the `canary` **source |
| 185 group**, causing its **components** to be loaded from the version of `base` |
| 186 defined in `sources/canary/base.cfg`. A *production* deployment can bind the |
| 187 same **application** to the `production` **source group**, causing its |
| 188 **components** to be loaded from `sources/production/base.cfg`. |
| 189 |
| 190 ### Source-Relative Paths |
| 191 |
| 192 Paths referenced within a source are referenced using "source-relative paths". |
| 193 These paths are: |
| 194 * Operating system independent, as they all use "/" as their delimiter. |
| 195 * Either **absolute** (starting with a "/") or **relative** (not starting with |
| 196 a "/"). Relative paths are relative to the configuration file in which they |
| 197 are specified. |
| 198 |
| 199 ## Checkout |
| 200 |
| 201 Prior to deployment, a user must sync the checked out sources with the |
| 202 configured sources using the `checkout` sub-command. This checks out *all* |
| 203 sources defined in the deployment layout and runs any configured initialization |
| 204 scripts to initialize them. |
| 205 |
| 206 A `checkout` is a manual operation. If the configured **sources** change, a new |
| 207 `checkout` operation must be performed to sync the on-disk checkout with the |
| 208 configured sources. |
| 209 |
| 210 Performing a checkout is simple: |
| 211 |
| 212 ```shell |
| 213 $ luci_deploy checkout |
| 214 ``` |
| 215 |
| 216 ### Local Checkout |
| 217 |
| 218 It is oftentimes useful for a user to stage (for testing) or deploy (for triage) |
| 219 code from their local checkout. Rather than editing the **source** files to use |
| 220 `file:///` URLs, the user may define a set of repository overrides in their |
| 221 [User Configuration](#user-configuration). Running the `checkout` sub-command |
| 222 with the `--local` flag will cause the checkout to prefer the user configuration |
| 223 repositroies to those configured in the **sources**. |
| 224 |
| 225 ```shell |
| 226 $ luci_deploy checkout --local |
| 227 ``` |
| 228 |
| 229 **NOTE**: The checkout will continue to use the local overrides until the |
| 230 `checkout` sub-command is re-run without the `--local` flag. However, also note |
| 231 that a checkout containing a local override is considered *tainted*. |
| 232 |
| 233 ## Deployment |
| 234 |
| 235 Deployment is accessed using the `deploy` sub-command. A user may deploy a |
| 236 single component or a full deployment. All deployment operations are run in |
| 237 stages, and, within each stage, in parallel. |
| 238 |
| 239 ```shell |
| 240 # Deploy all Components within a Deployment. |
| 241 $ luci_deploy deploy mydeployment |
| 242 |
| 243 # Deploy a specific set of Components. |
| 244 $ luci_deploy deploy mydeployment/component-a mydeployment/component-b |
| 245 ``` |
| 246 |
| 247 The `deploy` operation consists of several sub-stages: |
| 248 1. `stage`, where deployable **components** are generated from their sources and |
| 249 configurations. See [Staging](#staging) for more information. |
| 250 1. `localbuild`, where any local build operations are performed on the staged |
| 251 **components**. For some component types, this doubles as a sanity check prior |
| 252 to engaging remote services. |
| 253 1. `push`, where **components** are uploaded to their remote platforms. |
| 254 1. `commit`, where the uploaded **components** are activated and become live. |
| 255 |
| 256 For testing and debugging purposes, a deployment can be stopped prior to a full |
| 257 commit by using the `--stage` parameter. For example, to assert that all |
| 258 **components** in a deployment can be successfully staged and locally built, a |
| 259 user may run: |
| 260 |
| 261 ```shell |
| 262 $ luci_deploy deploy --stage=localbuild mydeployment |
| 263 ``` |
| 264 |
| 265 ## Component Configuration |
| 266 |
| 267 A **component** is a single buildable/deployable unit. **Components** are |
| 268 defined in **application** configuration files, and are specified as paths to |
| 269 `Component` messages within a **source**. `Component` messages are defined in |
| 270 [component.proto](api/deploy/component.proto). |
| 271 |
| 272 **Components** are intentionally defined within a **source**, as opposed to the |
| 273 deployment layout, because their specific composition and resource requirements |
| 274 will be versioned alongside their deployable code. For example, different |
| 275 versions of a Google AppEngine app may define different datastore indexes based |
| 276 on the underlying functionality of their code. |
| 277 |
| 278 ## User Configuration |
| 279 |
| 280 The user may include a configuration file in their home directory at |
| 281 `~/.luci_deploy.cfg`. If present, this file will be loaded alongside the |
| 282 layout configuration and incorporated into `luci_deploy` behavior. |
| 283 |
| 284 The user configuration file is a `UserConfig` text protobuf defined in |
| 285 [userconfig.proto](api/deploy/userconfig.proto). |
| 286 |
| 287 ## Staging |
| 288 |
| 289 A staging space is created for each deployable Component. Staging offers |
| 290 `luci_deploy` an isolated canvas with which it can the filesystem layouts for |
| 291 that Component's actual deployment tooling to operate. All file operations, |
| 292 generation, and structuring are performed in the staging state such that the |
| 293 resulting staging directory is available for tooling or humans to use. |
| 294 |
| 295 The staging space for a given Component depends on that Component's type. |
| 296 A staging layout is intended to be human-navigatable while conforming to any |
| 297 layout requirements imposed by that Component's deployment tooling. |
| 298 |
| 299 One goal that is enforced is that the actual checkout directories used by |
| 300 any given Component are considered read-only. This means that staging for |
| 301 Components which require generated files to exist alongside source must |
| 302 copy or mirror that source elsewhere. Some of the more convoluted aspects of |
| 303 staging layouts are the result of this requirement. |
| 304 |
| 305 ### AppEngine |
| 306 |
| 307 AppEngine deployments are composed of two sets of information: |
| 308 * Individual AppEngine modules, including the default module. |
| 309 * AppEngine Project-wide globals such as Index, Cron, Dispatch, and Queue |
| 310 settings. |
| 311 |
| 312 The individual Components are staged and deployed independently. The globals |
| 313 are composed of their respective settings in each individual Component |
| 314 associated with the AppEngine project regardless of whether that Component is |
| 315 actually being deployed. The aggregate globals are re-asserted once per |
| 316 cloud project at the end of module deployment. |
| 317 |
| 318 References to static content are flattened into static directories within the |
| 319 staging area. The generated YAML files are configured to point to this |
| 320 flattened space regardless of the original static content's location within |
| 321 the source. |
| 322 |
| 323 #### dev_appserver |
| 324 |
| 325 Moving Component configuration into protobufs and constructing composite |
| 326 GOPATH and generated configuration files prevents AppEngine tooling from |
| 327 working out of the box in the source repository. |
| 328 |
| 329 The offered solution is to manually stage the Components under test, then |
| 330 run tooling against the staged Component directories. The user may optionally |
| 331 install the staged paths (GOPATH, etc.) or use their default environment's |
| 332 paths. |
| 333 |
| 334 One downside to this solution is that staging operates on a snapshot of the |
| 335 repository, meaning that changes to the repository won't be reflected in the |
| 336 staged environment. The user can address this by either manually re-staging |
| 337 the Deployment when a file changes. The user may also structure their |
| 338 application such that the staged content doesn't change frequently (e.g., |
| 339 for Go, have a simple entry point that immediately imports the main app |
| 340 logic). Specific structures depend on the type of Component and how it is |
| 341 staged (see below). |
| 342 |
| 343 In the future, a command to bootstrap "dev_appserver" through the staged |
| 344 environment would be useful. |
| 345 |
| 346 #### Go on Classic AppEngine |
| 347 |
| 348 Go Classic AppEngine Components are deployed using the `appcfg.py` tool, |
| 349 which is the fastest available method to deploy such applications. |
| 350 |
| 351 Because the "app.yaml" file must exist alongside the deployed source, a |
| 352 stub entry point is generated in the staging area. This stub simply imports |
| 353 the actual entry point package. |
| 354 |
| 355 The Component's Sources which declare GOPATH presence are combined in a |
| 356 virutal GOPATH within the Component's staging area. This GOPATH is then |
| 357 installed and `appcfg.py`'s "update" method is invoked to upload the |
| 358 Component. |
| 359 |
| 360 #### Go on Managed VM |
| 361 |
| 362 Go AppEngine Managed VMs use a set of tools for deployment: |
| 363 * `aedeploy`, an AppEngine project tool which copies the various referenced |
| 364 sources across GOPATH entries into a single GOPATH hierarchy for Docker |
| 365 isolation. |
| 366 * `gcloud`, which engages the remote AppEngine service and offers deployment |
| 367 utility. |
| 368 * Behind the scenes, `gcloud` uses `docker` to build the actual deployed |
| 369 image from the source (`aedeploy` target) and assembled GOPATH. |
| 370 |
| 371 Because the "app.yaml" file must exist alongside the entry point code, the |
| 372 contents of the entry package are copied (via symlink) into a generated |
| 373 entry point package in the staging area. |
| 374 |
| 375 The Component's Sources which declare GOPATH presence are combined in a |
| 376 virutal GOPATH within the Component's staging area. This GOPATH is then |
| 377 collapsed by `aedeploy` when the Managed VM image is built. |
| 378 |
| 379 NOTE: because the entry point is actually a clone of the entry point |
| 380 package, "internal/" imports will not work. This can be easily worked around |
| 381 by having the entry point import another non-internal package within the |
| 382 project, and having that package act as the actual entry point. |
| 383 |
| 384 #### Static Content Module |
| 385 |
| 386 The `luci_deploy` supports the concept of a static content module. This is an |
| 387 AppEngine module whose sole purpose is to, via AppEngine handler definitions, |
| 388 map to uploaded static content. The utility of a static module is that it can |
| 389 be effortlessly updated without impacting actual AppEngine runtime processes. |
| 390 Static modules can be used in conjunction with module-referencing handlers in |
| 391 the default AppEngine module to create the effect of the default module |
| 392 actually hosting the static content. |
| 393 |
| 394 Static content modules are staged like other AppEngine modules, only with |
| 395 no running code. |
| 396 |
| 397 ### Container Engine / Kubernetes |
| 398 |
| 399 The `luci_deploy` supports depoying services to Google Container Engine, which |
| 400 is backed by Kubernetes. A project's Container Engine configuration consists |
| 401 of a series of Container Engine clusters, each of which hosts a series of |
| 402 homogenous machines. Kubernetes Pods, each of which are composed of one or |
| 403 more Kubernetes Components (i.e., Docker images), are deployed to one or more |
| 404 Google Container Engine Clusters. |
| 405 |
| 406 The Deployment Component defines a series of Container Engine Pods, an |
| 407 amalgam of a Kubernetes Pod definition and Container Engine requirements of |
| 408 that Kubernetes Pod. Each Kubernetes Pod's Component is built in its own |
| 409 staging area and deployed as part of that Pod to one or more Container Engine |
| 410 clusters. |
| 411 |
| 412 The Build phase of Container Engine deployment constructs a local Docker |
| 413 image of each Kubernetes Component. The Push phase pushes those images to |
| 414 the remote Docker image service. The Commit phase enacts the generated |
| 415 Kubernetes/ configuration which references those images on the Container |
| 416 Engine configuration. |
| 417 |
| 418 Container Engine management is done in two layers: firstly, the Container |
| 419 Engine configuration is managed with `gcloud` commands. This configures: |
| 420 |
| 421 * Which managed Clusters exist on Google Container Engine. |
| 422 * What scopes, system specs, and node count each Cluster has. |
| 423 |
| 424 Within a Cluster, several managed Kubernetes pods are deployed. The |
| 425 deployment is done using the `kubectl` tool, selecting the cluster using |
| 426 the "--context" flag to select the `gcloud`-generated Kubernetes context. |
| 427 |
| 428 Each `luci_deploy` Component (Kubernetes Pod, at this level) is managed as a |
| 429 Kubernetes Deployment (http://kubernetes.io/docs/user-guide/deployments/). A |
| 430 `luci_deploy`-managed Deployment will have the following metadata annotations: |
| 431 |
| 432 * "luci.managedBy", set to "luci-deploytool" |
| 433 * "luci.deploytool/version" set to the `deploytool` Deployment's version |
| 434 string. |
| 435 * "luci.deploytool/sourceVersion" set to the revision of the Deployment |
| 436 Component's source. |
| 437 |
| 438 The Kubernetes Deployment for a Component will be named |
| 439 "<project>--<component>". |
| 440 |
| 441 `luci_deploy`-driven Kubernetes depoyment is fairly straightforward: |
| 442 * Use "kubectl get deployments/<name>" to get the current Deployment state. |
| 443 * If there is a current Deployment, |
| 444 * If its "luci.managedBy" annotation doesn't equal "luci-deploytool", |
| 445 fail. The user must manually correct this situation. |
| 446 * Check if the "luci.deploytool/version" matches the container version. |
| 447 If it does, succeed. |
| 448 * Create/update the Deployment's configuration using "kubectl apply". |
| 449 |
| 450 #### Go Container Engine Pod Components |
| 451 |
| 452 Go Container Engine Pods use a set of tools for deployment: |
| 453 * `aedeploy`, an AppEngine project tool which copies the various referenced |
| 454 sources across GOPATH entries into a single GOPATH hierarchy for Docker |
| 455 isolation. |
| 456 * `gcloud`, which engages the remote AppEngine service and offers deployment |
| 457 utility. |
| 458 * `docker`, which is used to build and manage the Docker images. |
| 459 |
| 460 The Component's Sources which declare GOPATH presence are combined in a |
| 461 virutal GOPATH within the Component's staging area. This GOPATH is then |
| 462 collapsed by `aedeploy` when the Docker image is built. |
| 463 |
| 464 The actual Component is built directly from the Source using `aedeploy` and |
| 465 `docker build`. This is acceptable, since this is a read-only operation and |
| 466 will not modify the Source. |
| 467 |
| 468 ## To Do |
| 469 |
| 470 Following are some ideas of "planned" features that would be useful to add |
| 471 to `luci_deploy`: |
| 472 * Add a "manage" command to manage a specific Deployment and/or Component. |
| 473 Each invocation would load special set of subcommands based on that |
| 474 resource: |
| 475 * If the resource is a Deployment, query status? |
| 476 * Other common automatable management macros. |
| 477 * Offer a `dev_appserver` fallthrough to create a staging area and |
| 478 bootstrap `dev_appserver` for the named staged AppEngine Components. |
| OLD | NEW |