Skip to content

Commit

Permalink
Prototype CloudRund deployment with the new design of deployment conf…
Browse files Browse the repository at this point in the history
…iguration (#660)


**What this PR does / why we need it**:

**Which issue(s) this PR fixes**:

Fixes #

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
-->
```release-note
NONE
```

This PR was merged by Kapetanios.
  • Loading branch information
nghialv authored Aug 20, 2020
1 parent 6f1b4cb commit af35864
Show file tree
Hide file tree
Showing 19 changed files with 293 additions and 119 deletions.
2 changes: 1 addition & 1 deletion docs/content/en/docs/user-guide/configuration-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ spec:
| Field | Type | Description | Required |
|-|-|-|-|

### CloudRunCanaryRolloutStageOptions
### CloudRunPromoteStageOptions

| Field | Type | Description | Required |
|-|-|-|-|
Expand Down
21 changes: 6 additions & 15 deletions examples/cloudrun/analysis/.pipe.yaml
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
apiVersion: pipecd.dev/v1beta1
kind: CloudRunApp
spec:
input:
platform: managed
region: asia-northeast1
pipeline:
stages:
# Deploy workloads of the new version.
# But this is still receiving no traffic.
- name: CLOUDRUN_CANARY_ROLLOUT
# Change the traffic routing state where
# the new version will receive the specified percentage of traffic.
# This is known as multi-phase canary strategy.
- name: CLOUDRUN_TRAFFIC_ROUTING
# Promote new version to receive amount of traffic.
- name: CLOUDRUN_PROMOTE
with:
canary: 10
percent: 10
# Optional: We can also add an ANALYSIS stage to verify the new version.
# If this stage finds any not good metrics of the new version,
# a rollback process to the previous version will be executed.
- name: ANALYSIS
# Change the traffic routing state where
# the new version will receive 100% of the traffic.
- name: CLOUDRUN_TRAFFIC_ROUTING
# Promote new version to receive all traffic.
- name: CLOUDRUN_PROMOTE
with:
canary: 100
percent: 100
31 changes: 16 additions & 15 deletions examples/cloudrun/canary/.pipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,23 @@
apiVersion: pipecd.dev/v1beta1
kind: CloudRunApp
spec:
input:
platform: managed
region: asia-northeast1
pipeline:
stages:
# Deploy workloads of the new version.
# But this is still receiving no traffic.
- name: CLOUDRUN_CANARY_ROLLOUT
# Change the traffic routing state where
# the new version will receive the specified percentage of traffic.
# This is known as multi-phase canary strategy.
- name: CLOUDRUN_TRAFFIC_ROUTING
# Promote new version to receive amount of traffic.
- name: CLOUDRUN_PROMOTE
with:
canary: 10
# Change the traffic routing state where
# the new version will receive 100% of the traffic.
- name: CLOUDRUN_TRAFFIC_ROUTING
percent: 10
- name: WAIT
with:
canary: 100
duration: 30s
# Promote new version to receive amount of traffic.
- name: CLOUDRUN_PROMOTE
with:
percent: 50
- name: WAIT
with:
duration: 30s
# Promote new version to receive all traffic.
- name: CLOUDRUN_PROMOTE
with:
percent: 100
3 changes: 0 additions & 3 deletions examples/cloudrun/simple/.pipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,3 @@
apiVersion: pipecd.dev/v1beta1
kind: CloudRunApp
spec:
input:
platform: managed
region: asia-northeast1
9 changes: 8 additions & 1 deletion pkg/app/piped/cloudprovider/cloudrun/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "go_default_library",
Expand All @@ -23,3 +23,10 @@ go_library(
"@org_uber_go_zap//:go_default_library",
],
)

go_test(
name = "go_default_test",
size = "small",
srcs = ["servicemanifest_test.go"],
embed = [":go_default_library"],
)
2 changes: 1 addition & 1 deletion pkg/app/piped/cloudprovider/cloudrun/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func (c *client) Apply(ctx context.Context, sm ServiceManifest) (*Service, error
updatedService, err := call.Do()
if err != nil {
if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound {
return nil, fmt.Errorf("service %s was not found (%w)", name, ErrServiceNotFound)
return nil, fmt.Errorf("service %s was not found (%w), the service must be registered from Google CloudRun page", name, ErrServiceNotFound)
}
return nil, err
}
Expand Down
24 changes: 16 additions & 8 deletions pkg/app/piped/cloudprovider/cloudrun/servicemanifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,21 @@ func ParseServiceManifest(data []byte) (ServiceManifest, error) {
}, nil
}

func DecideRevisionName(m ServiceManifest, commit string) (string, error) {
containers, ok, err := unstructured.NestedSlice(m.u.Object, "spec", "template", "spec", "containers")
func DecideRevisionName(sm ServiceManifest, commit string) (string, error) {
tag, err := FindImageTag(sm)
if err != nil {
return "", err
}
tag = strings.ReplaceAll(tag, ".", "")

if len(commit) > 7 {
commit = commit[:7]
}
return fmt.Sprintf("%s-%s-%s", sm.Name, tag, commit), nil
}

func FindImageTag(sm ServiceManifest) (string, error) {
containers, ok, err := unstructured.NestedSlice(sm.u.Object, "spec", "template", "spec", "containers")
if err != nil {
return "", err
}
Expand All @@ -106,13 +119,8 @@ func DecideRevisionName(m ServiceManifest, commit string) (string, error) {
return "", fmt.Errorf("image was missing")
}
_, tag := parseContainerImage(image)
tag = strings.ReplaceAll(tag, ".", "")

if len(commit) > 7 {
commit = commit[:7]
}

return fmt.Sprintf("%s-%s-%s", m.Name, tag, commit), nil
return tag, nil
}

func parseContainerImage(image string) (name, tag string) {
Expand Down
15 changes: 15 additions & 0 deletions pkg/app/piped/cloudprovider/cloudrun/servicemanifest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2020 The PipeCD 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 cloudrun
9 changes: 8 additions & 1 deletion pkg/app/piped/executor/cloudrun/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "go_default_library",
Expand All @@ -12,3 +12,10 @@ go_library(
"//pkg/model:go_default_library",
],
)

go_test(
name = "go_default_test",
size = "small",
srcs = ["cloudrun_test.go"],
embed = [":go_default_library"],
)
173 changes: 152 additions & 21 deletions pkg/app/piped/executor/cloudrun/cloudrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ func Register(r registerer) {
}

r.Register(model.StageCloudRunSync, f)
r.Register(model.StageCloudRunCanaryRollout, f)
r.Register(model.StageCloudRunTrafficRouting, f)
r.Register(model.StageCloudRunPromote, f)

r.RegisterRollback(model.ApplicationKind_CLOUDRUN, f)
}
Expand Down Expand Up @@ -81,11 +80,8 @@ func (e *Executor) Execute(sig executor.StopSignal) model.StageStatus {
case model.StageCloudRunSync:
status = e.ensureSync(ctx)

case model.StageCloudRunCanaryRollout:
status = e.ensureCanaryRollout(ctx)

case model.StageCloudRunTrafficRouting:
status = e.ensureTrafficRouting(ctx)
case model.StageCloudRunPromote:
status = e.ensurePromote(ctx)

case model.StageRollback:
status = e.ensureRollback(ctx)
Expand All @@ -99,18 +95,11 @@ func (e *Executor) Execute(sig executor.StopSignal) model.StageStatus {
}

func (e *Executor) ensureSync(ctx context.Context) model.StageStatus {
var (
commit = e.Deployment.Trigger.Commit.Hash
appDir = filepath.Join(e.RepoDir, e.Deployment.GitPath.Path)
)

e.LogPersister.Infof("Loading service manifest at the triggered commit %s", commit)
sm, err := provider.LoadServiceManifest(appDir, e.config.Input.ServiceManifestFile)
if err != nil {
e.LogPersister.Errorf("Failed to load service manifest file (%v)", err)
commit := e.Deployment.Trigger.Commit.Hash
sm, ok := e.loadServiceManifest()
if !ok {
return model.StageStatus_STAGE_FAILURE
}
e.LogPersister.Info("Successfully loaded the service manifest")

e.LogPersister.Info("Generate a service manifest that configures all traffic to the revision specified at the triggered commit")
revision, err := provider.DecideRevisionName(sm, commit)
Expand Down Expand Up @@ -145,14 +134,156 @@ func (e *Executor) ensureSync(ctx context.Context) model.StageStatus {
return model.StageStatus_STAGE_SUCCESS
}

func (e *Executor) ensureCanaryRollout(ctx context.Context) model.StageStatus {
return model.StageStatus_STAGE_SUCCESS
}
func (e *Executor) ensurePromote(ctx context.Context) model.StageStatus {
options := e.StageConfig.CloudRunPromoteStageOptions
if options == nil {
e.LogPersister.Errorf("Malformed configuration for stage %s", e.Stage.Name)
return model.StageStatus_STAGE_FAILURE
}

// Determine the last deployed revision name.
lastDeployedCommit := e.Deployment.RunningCommitHash
if lastDeployedCommit == "" {
e.LogPersister.Errorf("Unable to determine the last deployed commit")
}

lastDeployedServiceManifest, ok := e.loadLastDeployedServiceManifest()
if !ok {
return model.StageStatus_STAGE_FAILURE
}

lastDeployedRevisionName, err := provider.DecideRevisionName(lastDeployedServiceManifest, lastDeployedCommit)
if err != nil {
e.LogPersister.Errorf("Unable to decide the last deployed revision name for the commit %s (%v)", lastDeployedCommit, err)
return model.StageStatus_STAGE_FAILURE
}

// Load triggered service manifest to apply.
commit := e.Deployment.Trigger.Commit.Hash
sm, ok := e.loadServiceManifest()
if !ok {
return model.StageStatus_STAGE_FAILURE
}

func (e *Executor) ensureTrafficRouting(ctx context.Context) model.StageStatus {
e.LogPersister.Infof("Generating a service manifest that configures traffic as: %d%% to new version, %d%% to old version", options.Percent, 100-options.Percent)
revisionName, err := provider.DecideRevisionName(sm, commit)
if err != nil {
e.LogPersister.Errorf("Unable to decide revision name for the commit %s (%v)", commit, err)
return model.StageStatus_STAGE_FAILURE
}

if err := sm.SetRevision(revisionName); err != nil {
e.LogPersister.Errorf("Unable to set revision name to service manifest (%v)", err)
return model.StageStatus_STAGE_FAILURE
}

revisions := []provider.RevisionTraffic{
{
RevisionName: revisionName,
Percent: options.Percent,
},
{
RevisionName: lastDeployedRevisionName,
Percent: 100 - options.Percent,
},
}
if err := sm.UpdateTraffic(revisions); err != nil {
e.LogPersister.Errorf("Unable to configure traffic (%v)", err)
return model.StageStatus_STAGE_FAILURE
}
e.LogPersister.Info("Successfully generated the appropriate service manifest")

e.LogPersister.Info("Start applying the service manifest")
client, err := provider.DefaultRegistry().Client(ctx, e.cloudProviderName, e.cloudProviderConfig, e.Logger)
if err != nil {
e.LogPersister.Errorf("Unable to create ClourRun client for the provider (%v)", err)
return model.StageStatus_STAGE_FAILURE
}
if _, err := client.Apply(ctx, sm); err != nil {
e.LogPersister.Errorf("Failed to apply the service manifest (%v)", err)
return model.StageStatus_STAGE_FAILURE
}
e.LogPersister.Info("Successfully applied the service manifest")

// TODO: Wait to ensure the traffic was fully configured.
return model.StageStatus_STAGE_SUCCESS
}

func (e *Executor) ensureRollback(ctx context.Context) model.StageStatus {
commit := e.Deployment.RunningCommitHash
if commit == "" {
e.LogPersister.Errorf("Unable to determine the last deployed commit to rollback. It seems this is the first deployment.")
return model.StageStatus_STAGE_FAILURE
}

sm, ok := e.loadLastDeployedServiceManifest()
if !ok {
return model.StageStatus_STAGE_FAILURE
}

e.LogPersister.Info("Generate a service manifest that configures all traffic to the last deployed revision")
revision, err := provider.DecideRevisionName(sm, commit)
if err != nil {
e.LogPersister.Errorf("Unable to decide revision name for the commit %s (%v)", commit, err)
return model.StageStatus_STAGE_FAILURE
}

if err := sm.SetRevision(revision); err != nil {
e.LogPersister.Errorf("Unable to set revision name to service manifest (%v)", err)
return model.StageStatus_STAGE_FAILURE
}

if err := sm.UpdateAllTraffic(revision); err != nil {
e.LogPersister.Errorf("Unable to configure all traffic to revision %s (%v)", revision, err)
return model.StageStatus_STAGE_FAILURE
}
e.LogPersister.Info("Successfully generated the appropriate service manifest")

e.LogPersister.Info("Start applying the service manifest")
client, err := provider.DefaultRegistry().Client(ctx, e.cloudProviderName, e.cloudProviderConfig, e.Logger)
if err != nil {
e.LogPersister.Errorf("Unable to create ClourRun client for the provider (%v)", err)
return model.StageStatus_STAGE_FAILURE
}
if _, err := client.Apply(ctx, sm); err != nil {
e.LogPersister.Errorf("Failed to apply the service manifest (%v)", err)
return model.StageStatus_STAGE_FAILURE
}
e.LogPersister.Info("Successfully applied the service manifest")

return model.StageStatus_STAGE_SUCCESS
}

func (e *Executor) loadServiceManifest() (provider.ServiceManifest, bool) {
var (
commit = e.Deployment.Trigger.Commit.Hash
appDir = filepath.Join(e.RepoDir, e.Deployment.GitPath.Path)
)

e.LogPersister.Infof("Loading service manifest at the triggered commit %s", commit)
sm, err := provider.LoadServiceManifest(appDir, e.config.Input.ServiceManifestFile)
if err != nil {
e.LogPersister.Errorf("Failed to load service manifest file (%v)", err)
return provider.ServiceManifest{}, false
}
e.LogPersister.Info("Successfully loaded the service manifest")

return sm, true
}

func (e *Executor) loadLastDeployedServiceManifest() (provider.ServiceManifest, bool) {
var (
commit = e.Deployment.RunningCommitHash
appDir = filepath.Join(e.RunningRepoDir, e.Deployment.GitPath.Path)
)

e.LogPersister.Infof("Loading service manifest at the last deployed commit %s", commit)
sm, err := provider.LoadServiceManifest(appDir, e.config.Input.ServiceManifestFile)
if err != nil {
e.LogPersister.Errorf("Failed to load service manifest file (%v)", err)
return provider.ServiceManifest{}, false
}
e.LogPersister.Info("Successfully loaded the service manifest")

return sm, true
}
Loading

0 comments on commit af35864

Please sign in to comment.