Skip to content

Commit

Permalink
Add 'skaffold apply' command (#5543)
Browse files Browse the repository at this point in the history
* Add 'skaffold apply' command

* add apply to instrumented commands

* add docs

* Update docs/content/en/docs/workflows/ci-cd.md

Co-authored-by: Brian de Alwis <bsd@acm.org>

* Update docs/content/en/docs/workflows/ci-cd.md

Co-authored-by: Brian de Alwis <bsd@acm.org>

* Update docs/content/en/docs/workflows/ci-cd.md

Co-authored-by: Brian de Alwis <bsd@acm.org>

* Update docs/content/en/docs/workflows/ci-cd.md

Co-authored-by: Brian de Alwis <bsd@acm.org>

* docs updates

Co-authored-by: Brian de Alwis <bsd@acm.org>
  • Loading branch information
nkubala and briandealwis authored Mar 17, 2021
1 parent 0fe5fd4 commit 5bf0323
Show file tree
Hide file tree
Showing 17 changed files with 372 additions and 47 deletions.
74 changes: 74 additions & 0 deletions cmd/skaffold/app/cmd/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
Copyright 2021 The Skaffold Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cmd

import (
"context"
"errors"
"fmt"
"io"
"os"

"github.com/spf13/cobra"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
)

// NewCmdApply describes the CLI command to apply manifests to a cluster.
func NewCmdApply() *cobra.Command {
return NewCmd("apply").
WithDescription("Apply hydrated manifests to a cluster").
WithExample("Hydrate Kubernetes pod manifest first", "render --output rendered-pod.yaml").
WithExample("Then create resources on your cluster from that hydrated manifest", "apply rendered-pod.yaml").
WithCommonFlags().
WithHouseKeepingMessages().
WithArgs(func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("`apply` requires at least one manifest argument")
}
return nil
}, doApply)
}

func doApply(ctx context.Context, out io.Writer, args []string) error {
// force set apply boolean to select default options in runner creation
opts.Apply = true
opts.HydratedManifests = args
if err := validateManifests(args); err != nil {
return err
}
return withRunner(ctx, out, func(r runner.Runner, configs []*latest.SkaffoldConfig) error {
return r.Apply(ctx, out)
})
}

func validateManifests(manifests []string) error {
for _, m := range manifests {
if _, err := os.Open(m); err != nil {
if errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("cannot find provided file %s", m)
}
return fmt.Errorf("unable to open provided file %s", m)
}
if !kubernetes.IsKubernetesManifest(m) {
return fmt.Errorf("%s is not a valid Kubernetes manifest", m)
}
}
return nil
}
1 change: 1 addition & 0 deletions cmd/skaffold/app/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ func NewSkaffoldCommand(out, err io.Writer) *cobra.Command {
NewCmdDeploy(),
NewCmdDelete(),
NewCmdRender(),
NewCmdApply(),
},
},
{
Expand Down
15 changes: 15 additions & 0 deletions cmd/skaffold/app/cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (

// Builder is used to build cobra commands.
type Builder interface {
WithArgs(cobra.PositionalArgs, func(context.Context, io.Writer, []string) error) *cobra.Command
WithDescription(description string) Builder
WithLongDescription(long string) Builder
WithExample(comment, command string) Builder
Expand Down Expand Up @@ -132,6 +133,20 @@ func (b *builder) NoArgs(action func(context.Context, io.Writer) error) *cobra.C
return &b.cmd
}

func (b *builder) WithArgs(f cobra.PositionalArgs, action func(context.Context, io.Writer, []string) error) *cobra.Command {
b.cmd.Args = f
b.cmd.RunE = func(_ *cobra.Command, args []string) error {
err := handleWellKnownErrors(action(b.cmd.Context(), b.cmd.OutOrStdout(), args))
// clean up server at end of the execution since post run hooks are only executed if
// RunE is successful
if shutdownAPIServer != nil {
shutdownAPIServer()
}
return err
}
return &b.cmd
}

func handleWellKnownErrors(err error) error {
if err == nil {
return err
Expand Down
16 changes: 8 additions & 8 deletions cmd/skaffold/app/cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ var flagRegistry = []Flag{
Value: &opts.Profiles,
DefValue: []string{},
FlagAddMethod: "StringSliceVar",
DefinedOn: []string{"dev", "run", "debug", "deploy", "render", "build", "delete", "diagnose"},
DefinedOn: []string{"dev", "run", "debug", "deploy", "render", "build", "delete", "diagnose", "apply"},
},
{
Name: "namespace",
Expand All @@ -92,7 +92,7 @@ var flagRegistry = []Flag{
Value: &opts.Namespace,
DefValue: "",
FlagAddMethod: "StringVar",
DefinedOn: []string{"dev", "run", "debug", "deploy", "render", "build", "delete"},
DefinedOn: []string{"dev", "run", "debug", "deploy", "render", "build", "delete", "apply"},
},
{
Name: "default-repo",
Expand Down Expand Up @@ -201,7 +201,7 @@ var flagRegistry = []Flag{
"debug": true,
},
FlagAddMethod: "BoolVar",
DefinedOn: []string{"dev", "run", "debug", "deploy"},
DefinedOn: []string{"dev", "run", "debug", "deploy", "apply"},
IsEnum: true,
},
{
Expand All @@ -210,7 +210,7 @@ var flagRegistry = []Flag{
Value: &opts.Force,
DefValue: false,
FlagAddMethod: "BoolVar",
DefinedOn: []string{"deploy", "dev", "run", "debug"},
DefinedOn: []string{"deploy", "dev", "run", "debug", "apply"},
IsEnum: true,
},
{
Expand Down Expand Up @@ -264,7 +264,7 @@ var flagRegistry = []Flag{
Value: &opts.StatusCheck,
DefValue: true,
FlagAddMethod: "BoolVar",
DefinedOn: []string{"dev", "debug", "deploy", "run"},
DefinedOn: []string{"dev", "debug", "deploy", "run", "apply"},
IsEnum: true,
},
{
Expand All @@ -291,23 +291,23 @@ var flagRegistry = []Flag{
Value: &opts.GlobalConfig,
DefValue: "",
FlagAddMethod: "StringVar",
DefinedOn: []string{"run", "dev", "debug", "build", "deploy", "delete", "diagnose"},
DefinedOn: []string{"run", "dev", "debug", "build", "deploy", "delete", "diagnose", "apply"},
},
{
Name: "kube-context",
Usage: "Deploy to this Kubernetes context",
Value: &opts.KubeContext,
DefValue: "",
FlagAddMethod: "StringVar",
DefinedOn: []string{"build", "debug", "delete", "deploy", "dev", "run", "filter"},
DefinedOn: []string{"build", "debug", "delete", "deploy", "dev", "run", "filter", "apply"},
},
{
Name: "kubeconfig",
Usage: "Path to the kubeconfig file to use for CLI requests.",
Value: &opts.KubeConfig,
DefValue: "",
FlagAddMethod: "StringVar",
DefinedOn: []string{"build", "debug", "delete", "deploy", "dev", "run", "filter"},
DefinedOn: []string{"build", "debug", "delete", "deploy", "dev", "run", "filter", "apply"},
},
{
Name: "tag",
Expand Down
49 changes: 49 additions & 0 deletions docs/content/en/docs/references/cli/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Pipeline building blocks for CI/CD:
deploy Deploy pre-built artifacts
delete Delete the deployed application
render [alpha] Perform all image builds, and output rendered Kubernetes manifests
apply Apply hydrated manifests to a cluster
Getting started with a new project:
init [alpha] Generate configuration for deploying an application
Expand Down Expand Up @@ -104,6 +105,54 @@ Env vars:
* `SKAFFOLD_UPDATE_CHECK` (same as `--update-check`)
* `SKAFFOLD_VERBOSITY` (same as `--verbosity`)

### skaffold apply

Apply hydrated manifests to a cluster

```
Examples:
# Hydrate Kubernetes pod manifest first
skaffold render --output rendered-pod.yaml
# Then create resources on your cluster from that hydrated manifest
skaffold apply rendered-pod.yaml
Options:
-c, --config='': File for global configurations (defaults to $HOME/.skaffold/config)
-f, --filename='skaffold.yaml': Path or URL to the Skaffold config file
--force=false: Recreate Kubernetes resources if necessary for deployment, warning: might cause downtime!
--kube-context='': Deploy to this Kubernetes context
--kubeconfig='': Path to the kubeconfig file to use for CLI requests.
-m, --module=[]: Filter Skaffold configs to only the provided named modules
-n, --namespace='': Run deployments in the specified namespace
-p, --profile=[]: Activate profiles by name (prefixed with `-` to disable a profile)
--remote-cache-dir='': Specify the location of the git repositories cache (default $HOME/.skaffold/repos)
--status-check=true: Wait for deployed resources to stabilize
--tail=false: Stream logs from deployed objects
Usage:
skaffold apply [options]
Use "skaffold options" for a list of global command-line options (applies to all commands).
```
Env vars:

* `SKAFFOLD_CONFIG` (same as `--config`)
* `SKAFFOLD_FILENAME` (same as `--filename`)
* `SKAFFOLD_FORCE` (same as `--force`)
* `SKAFFOLD_KUBE_CONTEXT` (same as `--kube-context`)
* `SKAFFOLD_KUBECONFIG` (same as `--kubeconfig`)
* `SKAFFOLD_MODULE` (same as `--module`)
* `SKAFFOLD_NAMESPACE` (same as `--namespace`)
* `SKAFFOLD_PROFILE` (same as `--profile`)
* `SKAFFOLD_REMOTE_CACHE_DIR` (same as `--remote-cache-dir`)
* `SKAFFOLD_STATUS_CHECK` (same as `--status-check`)
* `SKAFFOLD_TAIL` (same as `--tail`)

### skaffold build

Build the artifacts
Expand Down
75 changes: 48 additions & 27 deletions docs/content/en/docs/workflows/ci-cd.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,26 @@ linkTitle: "Continuous Delivery"
weight: 40
---

Skaffold offers several sub-commands for its workflows that make it quite flexible when integrating with CI/CD pipelines.
Skaffold provides several features and sub-command "building blocks" that make it very useful for integrating with (or creating entirely new) CI/CD pipelines.
The ability to use the same `skaffold.yaml` for iterative development and continuous delivery eases handing off an application from a development team to an ops team.

Let's start with the simplest use case: a single, full deployment of your application.

## `skaffold run`
{{< maturity "run" >}}

`skaffold run` is a single command for a one-off deployment. It includes all the following phases as it builds, tags, deploys and waits for the deployment to succeed if specified.
We recommend `skaffold run` for a simple Continuous Delivery setup, where it is sufficient to have a single step that deploys from version control to a cluster.
For more sophisticated Continuous Delivery pipelines, Skaffold offers building blocks that are described next:
`skaffold run` is a single command for a one-off deployment. It runs through every major phase of the Skaffold lifecycle: building your application images, tagging these images (and optionally pushing them to a remote registry), deploying your application to the target cluster, and monitoring the created resources for readiness.

We recommend `skaffold run` for the simplest Continuous Delivery setup, where it is sufficient to have a single step that deploys from version control to a cluster.

For more sophisticated Continuous Delivery pipelines, Skaffold offers building blocks:

- [healthcheck]({{<relref "/docs/workflows/ci-cd#waiting-for-skaffold-deployments-using-healthcheck">}}) -
wait for `deployments` to stabilize and succeed only if all deployments are successful
- [`skaffold build`]({{<relref "/docs/workflows/ci-cd#skaffold-build-skaffold-deploy">}}) - build, tag and push artifacts to a registry
- [`skaffold deploy`]({{<relref "/docs/workflows/ci-cd#skaffold-build-skaffold-deploy">}}) - deploy built artifacts to a cluster
- [`skaffold render`]({{<relref "/docs/workflows/ci-cd#skaffold-render">}}) - export the transformed Kubernetes manifests for GitOps workflows
- [`skaffold render`]({{<relref "/docs/workflows/ci-cd#skaffold-render-skaffold-apply">}}) - export the transformed Kubernetes manifests for GitOps workflows
- [`skaffold apply`]({{<relref "/docs/workflows/ci-cd#skaffold-render-skaffold-apply">}}) - send hydrated Kubernetes manifests to the API server to create resources on the target cluster

## Waiting for Skaffold deployments using `healthcheck`
{{< maturity "deploy.status_check" >}}
Expand Down Expand Up @@ -106,7 +112,7 @@ Waiting for deployments to stabilize
FATA[0006] 1/1 deployment(s) failed
```

## `skaffold build | skaffold deploy`
## Traditional continuous delivery: `skaffold build | skaffold deploy`

`skaffold build` will build your project's artifacts, and push the build images to the specified registry. If your project is already configured to run with Skaffold, `skaffold build` can be a very lightweight way of setting up builds for your CI pipeline. Passing the `--file-output` flag to Skaffold build will also write out your built artifacts in JSON format to a file on disk, which can then by passed to `skaffold deploy` later on. This is a great way of "committing" your artifacts when they have reached a state that you're comfortable with, especially for projects with multiple artifacts for multiple services.

Expand Down Expand Up @@ -147,42 +153,57 @@ Starting deploy...
```


## `skaffold render`
{{< maturity "render" >}}
## GitOps-style continuous delivery: `skaffold render` | `skaffold apply`
{{< maturity "apply" >}}

GitOps-based CD pipelines traditionally see fully-hydrated Kubernetes manifests committed to a configuration Git repository (separate from the application source), which triggers a deployment pipeline that applies the changes to resources on the cluster. Skaffold has two built-in commands that enable easy GitOps pipeline workflows - `skaffold render` and `skaffold apply`.

`skaffold render` builds all application images from your artifacts, templates the newly-generated image tags into your Kubernetes manifests (based on your project's deployment configuration), and then prints out the final hydrated manifests to a file or your terminal. This allows you to capture the full, declarative state of your application in configuration rather than actually applying changes to your cluster, and use this configuration in a GitOps pipeline by committing it to a separate Git repository.

`skaffold apply` consumes one or more fully-hydrated Kubernetes manifests, and then sends the results directly to the Kubernetes control plane via `kubectl` to create resources on the target cluster. After creating the resources on your cluster, `skaffold apply` uses Skaffold's built-in health checking to monitor the created resources for readiness. See [resource health checks]({{<relref "/docs/workflows/ci-cd#waiting-for-skaffold-deployments-using-healthcheck">}}) for more information on how Skaffold's resource health checking works.

*Note: `skaffold apply` always uses `kubectl` to deploy resources to a target cluster, regardless of deployment configuration in the provided skaffold.yaml. Only a small subset of deploy configuration is honored when running `skaffold apply`:*
* deploy.statusCheckDeadlineSeconds
* deploy.kubeContext
* deploy.logs.prefix
* deploy.kubectl.flags
* deploy.kubectl.defaultNamespace
* deploy.kustomize.flags
* deploy.kustomize.defaultNamespace

{{<alert title="Note">}}
`skaffold apply` attempts to honor the deployment configuration mentioned above. But when conflicting configuration is detected in a multi-configuration project, `skaffold apply` will not work.
{{</alert>}}

`skaffold apply` works with any arbitrary Kubernetes YAML, whether it was generated by Skaffold or not, making it an ideal counterpart to `skaffold render`.

Skaffold also has another built-in command, `skaffold render`, that will perform builds on all artifacts in your project, template the newly built image tags into your Kubernetes deployment configuration files (based on your configured deployer), and instead of sending these through the deployment process, print out the final deployment artifacts. This allows you to snapshot your project's builds, but also integrate those builds into your deployment configs to snapshot your deployment as well. This can be very useful when integrating with GitOps based workflows: these templated deployment configurations can be committed to a Git repository as a way to deploy using GitOps.
### Example: Hydrating Kubernetes resources using `skaffold render`, then sending them to the cluster using `skaffold apply`:

Example of running `skaffold render` to render Kubernetes manifests, then sending them directly to `kubectl`:
First, use `skaffold render` to hydrate the Kubernetes resource file with a newly-built image tag:

Running `skaffold render --output render.txt && cat render.txt` outputs:
```code
$ skaffold render --output render.yaml
```
```yaml
# render.yaml
apiVersion: v1
kind: Pod
metadata:
name: getting-started
namespace: default
spec:
containers:
- image: gcr.io/k8s-skaffold/skaffold-example:v0.41.0-57-gbee90013@sha256:eeffb639f53368c4039b02a4d337bde44e3acc728b309a84353d4857ee95c369
- image: gcr.io/k8s-skaffold/skaffold-example:v1.19.0-89-gdbedd2a20-dirty
name: getting-started
```

We can then pipe this yaml to kubectl:
```code
cat render.txt | kubectl apply -f -
```
which shows
```
pod/getting-started configured
```
Or, if we want to skip the file writing altogether:
Then, we can apply this output directly to the cluster:

```code
skaffold render | kubectl apply -f -
```
$ skaffold apply render.yaml
gives us the one line output telling us the only thing we need to know:
```code
pod/getting-started configured
Starting deploy...
- pod/getting-started created
Waiting for deployments to stabilize...
Deployments stabilized in 49.277055ms
```
7 changes: 7 additions & 0 deletions docs/data/maturity.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
"description": "Control API, Event API and State API",
"url": "/docs/design/api"
},
"apply": {
"deploy": "x",
"area": "Deploy",
"feature": "Apply",
"maturity": "alpha",
"description": "Use hydrated Kubernetes manifests to create resources on the cluster"
},
"build.buildpacks": {
"build": "x",
"area": "Build",
Expand Down
Loading

0 comments on commit 5bf0323

Please sign in to comment.