Here's a detailed guide to setting up a Canary deployment on Google Cloud using Terraform and Cloud Deploy. This example demonstrates deploying a sample application image to a Google Kubernetes Engine (GKE) cluster, utilizing Canary deployment strategies to gradually roll out updates
Setting Up Terraform Configuration Files
provider.tf
terraform { required_providers { google = { source = "hashicorp/google" version = "5.18.0" } } } provider "google" { project = var.project_id region = var.region zone = var.zone } provider "google-beta" { project = var.project_id region = var.region zone = var.zone }
variable.tf
variable "project_id" {
default = ""
}
variable "region" {
default = ""
}
variable "zone" {
default = ""
}
variable "sec_region" {
default = ""
}
variable "sec_zone" {
default = ""
}
terraform.tfvars
project_id = <PROYECT-ID>
region = "us-central1"
zone = "us-central1-a"
sec_region = "us-west1"
sec_zone = "us-west1-a"
Service Account Configuration
serviceaccount.tf
resource "google_service_account" "gke_service_account" {
project = var.project_id
account_id = "gke-service-account-id"
display_name = "Service Account for Canary-deploy"
}
output "service_account_email" {
value = google_service_account.gke_service_account.email
}
Network Configuration
network.tf
resource "google_compute_network" "nw1-vpc" {
name = "nw1-vpc"
auto_create_subnetworks = false
mtu = 1460
}
resource "google_compute_subnetwork" "nw1-subnet1" {
name = "nw1-vpc-sub1-${var.region}"
network = google_compute_network.nw1-vpc.id
ip_cidr_range = "10.10.1.0/24"
region = var.region
private_ip_google_access = true
}
resource "google_compute_subnetwork" "nw1-subnet2" {
name = "nw2-vpc-sub3-euro-west2"
network = google_compute_network.nw1-vpc.id
ip_cidr_range = "10.10.2.0/24"
region = "europe-west2"
private_ip_google_access = true
}
resource "google_compute_firewall" "nw1-ssh-icmp-allow" {
name = "nw1-vpc-ssh-allow"
network = google_compute_network.nw1-vpc.id
allow {
protocol = "icmp"
}
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = ["39.33.11.48/32"]
target_tags = ["nw1-vpc-ssh-allow"]
priority = 1000
}
resource "google_compute_firewall" "nw1-internal-allow" {
name = "nw1-vpc-internal-allow"
network = google_compute_network.nw1-vpc.id
allow {
protocol = "icmp"
}
allow {
protocol = "udp"
ports = ["0-65535"]
}
allow {
protocol = "tcp"
ports = ["0-65535"]
}
source_ranges = ["10.10.0.0/16"]
priority = 1100
}
resource "google_compute_firewall" "nw1-iap-allow" {
name = "nw1-vpc-iap-allow"
network = google_compute_network.nw1-vpc.id
allow {
protocol = "icmp"
}
allow {
protocol = "tcp"
ports = ["0-65535"]
}
source_ranges = ["35.235.240.0/20"]
priority = 1200
}
resource "google_compute_address" "natpip" {
name = "ipv4-address"
region = "europe-west2"
}
resource "google_compute_router" "router1" {
name = "nat-router1"
region = "europe-west2"
network = google_compute_network.nw1-vpc.id
bgp {
asn = 64514
}
}
resource "google_compute_router_nat" "nat1" {
name = "natgw1"
router = google_compute_router.router1.name
region = "europe-west2"
nat_ip_allocate_option = "MANUAL_ONLY"
nat_ips = [google_compute_address.natpip.self_link]
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
min_ports_per_vm = 256
max_ports_per_vm = 512
log_config {
enable = true
filter = "ERRORS_ONLY"
}
}
resource "google_compute_global_address" "private_ip_address" {
name = google_compute_network.nw1-vpc.name
purpose = "VPC_PEERING"
address_type = "INTERNAL"
prefix_length = 16
network = google_compute_network.nw1-vpc.name
}
resource "google_service_networking_connection" "private_vpc_connection" {
network = google_compute_network.nw1-vpc.id
service = "servicenetworking.googleapis.com"
reserved_peering_ranges = [google_compute_global_address.private_ip_address.name]
}
IAM Configuration
iam.tf
resource "google_project_iam_member" "member-role" { depends_on = [google_service_account.gke_service_account] for_each = toset([ "roles/iam.serviceAccountTokenCreator", "roles/clouddeploy.jobRunner", "roles/container.developer", ]) role = each.key project = var.project_id member = "serviceAccount:${google_service_account.gke_service_account.email}" }
GKE Cluster Configuration
main.tf
resource "google_container_cluster" "primary" {
depends_on = [google_service_account.gke_service_account]
name = "canary-quickstart-cluster"
location = var.region
deletion_protection = false
remove_default_node_pool = true
initial_node_count = 1
enable_shielded_nodes = true
node_config {
service_account = google_service_account.gke_service_account.email
oauth_scopes = [
"https://www.googleapis.com/auth/cloud-platform"
]
labels = {
foo = "bar"
}
tags = ["foo", "bar"]
}
network = google_compute_network.nw1-vpc.self_link
subnetwork = google_compute_subnetwork.nw1-subnet1.self_link
}
resource "google_container_node_pool" "primary_preemptible_nodes" {
depends_on = [
google_container_cluster.primary,
google_service_account.gke_service_account
]
name = "my-node-pool"
location = var.region
cluster = google_container_cluster.primary.name
node_count = 1
node_config {
preemptible = true
machine_type = "e2-medium"
service_account = google_service_account.gke_service_account.email
oauth_scopes = [
"https://www.googleapis.com/auth/cloud-platform"
]
}
}
Running Terraform Commands
Initialize Terraform:
terraform init
Validate the configuration:
terraform validate
Apply the configuration:
terraform apply -auto-approve
Deploying the Application
Skaffold Configuration
apiVersion: skaffold/v4beta7 kind: Config manifests: rawYaml: - kubernetes.yaml deploy: kubectl: {}
Kubernetes Manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
labels:
app: my-app
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: nginx
image: my-app-image
---
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: default
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
Cloud Deploy Pipeline
apiVersion: deploy.cloud.google.com/v1
kind: DeliveryPipeline
metadata:
name: my-canary-demo-app-1
description: main application pipeline
serialPipeline:
stages:
- targetId: prod
profiles: []
strategy:
canary:
runtimeConfig:
kubernetes:
serviceNetworking:
service: "my-service"
deployment: "my-deployment"
canaryDeployment:
percentages: [50]
verify: false
---
apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
name: prod
description: prod GKE cluster
gke:
cluster: projects/PROJECT_ID/locations/us-central1/clusters/canary-quickstart-cluster
Registering and Creating Releases
gcloud deploy apply --file=clouddeploy.yaml --region=us-central1 --project=PROJECT_ID
gcloud deploy releases create test-release-001 \
--project=PROJECT_ID \
--region=us-central1 \
--delivery-pipeline=my-canary-demo-app-1 \
--images=my-app-image=gcr.io/google-containers/nginx@sha256:f49a843c290594dcf4d193535d1f4ba8af7d56cea2cf79d1e9554f077f1e7aaa
- In the pipeline visualization, click Advance to stable.
- When prompted, click Advance to confirm.
gcloud deploy releases create test-release-002 \
--project=PROJECT_ID \
--region=us-central1 \
--delivery-pipeline=my-canary-demo-app-1 \
--images=my-app-image=gcr.io/google-containers/nginx@sha256:f49a843c290594dcf4d193535d1f4ba8af7d56cea2cf79d1e9554f077f1e7aaa