Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prototype CloudRund deployment with the new design of deployment configuration #660

Merged
merged 3 commits into from
Aug 20, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
var options = e.StageConfig.CloudRunPromoteStageOptions
nghialv marked this conversation as resolved.
Show resolved Hide resolved
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