HA MySQL cluster deployment on GKE (#2061)
* MySQL pattern on GKE * Use terraform managed password * Use hardcoded network references * Explain why Cloud NAT * Rename versions_override.tf * Fix subnet reference * Fix password * Fix MysQL connect commands * Remove self-link * Update README.md * Add TOC and Variables table * Fix outputs * Fix linter --------- Co-authored-by: Julio Castillo <jccb@google.com>
This commit is contained in:
parent
50c7d3c0e9
commit
597579fa2b
|
@ -0,0 +1,105 @@
|
|||
# Highly Available MySQL cluster on GKE
|
||||
|
||||
<!-- BEGIN TOC -->
|
||||
- [Architecture](#architecture)
|
||||
- [Usage](#usage)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Examples](#examples)
|
||||
- [Default MySQL cluster on GKE with Docker Hub connectivity using Fleet Connection endpoint](#default-mysql-cluster-on-gke-with-docker-hub-connectivity-using-fleet-connection-endpoint)
|
||||
- [Customized MySQL cluster using Remote Repository and Fleet Connection endpoint](#customized-mysql-cluster-using-remote-repository-and-fleet-connection-endpoint)
|
||||
- [Default cluster using provided static IP address](#default-cluster-using-provided-static-ip-address)
|
||||
- [Variables](#variables)
|
||||
- [Outputs](#outputs)
|
||||
<!-- END TOC -->
|
||||
|
||||
<a href="https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/GoogleCloudPlatform/cloud-foundation-fabric.git&cloudshell_tutorial=mysql/tutorial.md&cloudshell_git_branch=master&cloudshell_workspace=blueprints/gke/patterns&show=ide%2Cterminal">
|
||||
<img width="200px" src="../../../../assets/images/cloud-shell-button.png">
|
||||
</a>
|
||||
|
||||
## Architecture
|
||||
MySQL cluster is exposed using Regional Internal TCP Passthrough Load Balancer either on random or on provided static IP address. Services are listening on four different ports depending on protocol and intended usage:
|
||||
* 6446 - read/write access using MySQL protocol (targets primary instance)
|
||||
* 6447 - read-only access using MySQL protocol (targets any instance)
|
||||
* 64460 - read/write access using MySQLx protocol (targets primary instance)
|
||||
* 64470 - read-only access using MySQLx protocol (targets any instance)
|
||||
|
||||
Behind Load Balancer there are pods (by default - 2 pods) running MySQL router image that are responsible to route the traffic to proper MySQL cluster member. Router learns about MySQL cluster topology by contacting any MySQL server pod using `mysql-bootstrap` ClusterIP Service during startup and checks periodically for any changes in topology.
|
||||
|
||||
MySQL's instances are provisioned using StatefulSet and their Pod DNS identity is provided by Headless Service `mysql`. Those DNS names (`dbc1-${index}.mysql.${namespace}.svc.cluster.local`) are used when configuring the cluster and by MySQL router when connecting to desired instance. By default, there are 3 instances provisioned, which is required minimum to obtain highly available solution. Each instance in StatefulSet attaches Physical Volume to store database which persists removal of the Pod or changing the number of instances. These Physical Volumes are kept even when StatefulSet is removed and require manual removal.
|
||||
|
||||
The database admin password is generated in Terraform and stored as a Kubernetes Secret.
|
||||
|
||||
`mysql-server` Pods are spread across different zones using `topologySpreadConstraints` with `maxSkew` of 1, `minDomains` of 3 and Pod antiAffinity preventing the Pods to run on the same host. This permits running two nodes in one zone (but on different hosts) in case of one zone failure in 3-zoned region.
|
||||
|
||||
`mysql-router` Pods hava affinity to run in the same zones as `mysql-server` nodes and antiAffinity to run on the same host (required) or zone (preferred) as other `mysql-router` . With two instances of `mysql-router` this might result in 2 instances running in the same region
|
||||
|
||||
## Usage
|
||||
### Prerequisites
|
||||
* GKE cluster is already provisioned and access to it using `kubectl` is configured. You can use [autopilot-cluster](../autopilot-cluster) blueprint to create such cluster.
|
||||
* kubectl configuration obtained either by `gcloud container clusters get-credentials` or `gcloud container fleet memberships get-credentials`
|
||||
* Cluster node have access to Oracle images `mysql/mysql-server` and `mysql/mysql-router`
|
||||
* Access to the cluster's API from where `terraform` is run either using GKE API endpoint of Fleet endpoint. [autopilot-cluster](../autopilot-cluster) blueprint provisions and provides the link to Fleet endpoint.
|
||||
* (optional) static IP address to be used by LoadBalancer that exposes MySQL
|
||||
|
||||
## Examples
|
||||
### Default MySQL cluster on GKE with Docker Hub connectivity using Fleet Connection endpoint
|
||||
```hcl
|
||||
credentials_config = {
|
||||
fleet_host = "https://connectgateway.googleapis.com/v1/projects/.../locations/global/gkeMemberships/..." # provided by ../autopilot-cluster blueprint
|
||||
}
|
||||
|
||||
# tftest skip
|
||||
```
|
||||
|
||||
### Customized MySQL cluster using Remote Repository and Fleet Connection endpoint
|
||||
```hcl
|
||||
credentials_config = {
|
||||
fleet_host = "https://connectgateway.googleapis.com/v1/projects/.../locations/global/gkeMemberships/..." # provided by ../autopilot-cluster blueprint
|
||||
}
|
||||
|
||||
registry_path = "europe-west8-docker.pkg.dev/.../..."
|
||||
mysql_config = {
|
||||
db_replicas = 8
|
||||
db_cpu = "750m"
|
||||
db_memory = "2Gi"
|
||||
db_database_size = "4Gi"
|
||||
router_replicas = 3
|
||||
router_cpu = "250m"
|
||||
router_memory = "1Gi"
|
||||
version = "8.0.30"
|
||||
}
|
||||
|
||||
# tftest skip
|
||||
```
|
||||
|
||||
### Default cluster using provided static IP address
|
||||
|
||||
```hcl
|
||||
credentials_config = {
|
||||
fleet_host = "https://connectgateway.googleapis.com/v1/projects/.../locations/global/gkeMemberships/..." # provided by ../autopilot-cluster blueprint
|
||||
}
|
||||
mysql_config = {
|
||||
ip_address = "10.0.0.2"
|
||||
}
|
||||
# tftest skip
|
||||
```
|
||||
<!-- BEGIN TFDOC -->
|
||||
## Variables
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [created_resources](variables.tf#L17) | IDs of the resources created by autopilot cluster to be consumed here. | <code title="object({ vpc_id = string subnet_id = string })">object({…})</code> | ✓ | |
|
||||
| [credentials_config](variables.tf#L26) | Configure how Terraform authenticates to the cluster. | <code title="object({ fleet_host = optional(string) kubeconfig = optional(object({ context = optional(string) path = optional(string, "~/.kube/config") })) })">object({…})</code> | ✓ | |
|
||||
| [project_id](variables.tf#L69) | Project to deploy bastion host. | <code>string</code> | ✓ | |
|
||||
| [region](variables.tf#L74) | Region used for cluster and network resources. | <code>string</code> | ✓ | |
|
||||
| [mysql_config](variables.tf#L45) | Configure MySQL server and router instances. | <code title="object({ db_cpu = optional(string, "500m") db_database_size = optional(string, "10Gi") db_memory = optional(string, "1Gi") db_replicas = optional(number, 3) ip_address = optional(string) router_replicas = optional(number, 2) # cannot be higher than number of the zones in region router_cpu = optional(string, "500m") router_memory = optional(string, "2Gi") version = optional(string, "8.0.34") # latest is 8.0.34, originally was with 8.0.28 / 8.0.27, })">object({…})</code> | | <code>{}</code> |
|
||||
| [namespace](variables.tf#L62) | Namespace used for MySQL cluster resources. | <code>string</code> | | <code>"mysql1"</code> |
|
||||
| [registry_path](variables.tf#L79) | Repository path for images. Default is to use Docker Hub images. | <code>string</code> | | <code>"docker.io"</code> |
|
||||
| [templates_path](variables.tf#L86) | Path where manifest templates will be read from. Set to null to use the default manifests. | <code>string</code> | | <code>null</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
| name | description | sensitive |
|
||||
|---|---|:---:|
|
||||
| [mysql_password](outputs.tf#L16) | Password for the MySQL root user. | ✓ |
|
||||
<!-- END TFDOC -->
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
locals {
|
||||
manifest_template_parameters = {
|
||||
mysql_config = var.mysql_config
|
||||
namespace = helm_release.mysql-operator.namespace
|
||||
registry_path = var.registry_path
|
||||
mysql_password = random_password.mysql_password.result
|
||||
}
|
||||
stage_1_templates = [
|
||||
for f in fileset(local.wl_templates_path, "01*yaml") :
|
||||
"${local.wl_templates_path}/${f}"
|
||||
]
|
||||
stage_2_templates = [
|
||||
for f in fileset(local.wl_templates_path, "02*yaml") :
|
||||
"${local.wl_templates_path}/${f}"
|
||||
]
|
||||
|
||||
wl_templates_path = (
|
||||
var.templates_path == null
|
||||
? "${path.module}/manifest-templates"
|
||||
: pathexpand(var.templates_path)
|
||||
)
|
||||
}
|
||||
|
||||
resource "random_password" "mysql_password" {
|
||||
length = 28
|
||||
lower = true
|
||||
numeric = true
|
||||
upper = true
|
||||
special = false
|
||||
}
|
||||
|
||||
resource "helm_release" "mysql-operator" {
|
||||
name = "my-mysql-operator"
|
||||
repository = "https://mysql.github.io/mysql-operator/"
|
||||
chart = "mysql-operator"
|
||||
namespace = var.namespace
|
||||
create_namespace = true
|
||||
set {
|
||||
name = "envs.k8sClusterDomain"
|
||||
value = "cluster.local" # avoid lookups during operator startups which sometimes fail
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubectl_manifest" "dependencies" {
|
||||
for_each = toset(local.stage_1_templates)
|
||||
yaml_body = templatefile(each.value, local.manifest_template_parameters)
|
||||
|
||||
override_namespace = helm_release.mysql-operator.namespace
|
||||
|
||||
timeouts {
|
||||
create = "30m"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
resource "kubectl_manifest" "deploy_cluster" {
|
||||
for_each = toset(local.stage_2_templates)
|
||||
yaml_body = templatefile(each.value, local.manifest_template_parameters)
|
||||
|
||||
override_namespace = helm_release.mysql-operator.namespace
|
||||
|
||||
timeouts {
|
||||
create = "30m"
|
||||
}
|
||||
depends_on = [kubectl_manifest.dependencies]
|
||||
}
|
||||
|
||||
module "bastion" {
|
||||
source = "../../../../modules/compute-vm"
|
||||
|
||||
name = "bastion"
|
||||
network_interfaces = [{
|
||||
addresses = {
|
||||
internal = "10.0.0.10"
|
||||
}
|
||||
network = var.created_resources.vpc_id
|
||||
subnetwork = var.created_resources.subnet_id
|
||||
}]
|
||||
project_id = var.project_id
|
||||
zone = "${var.region}-b"
|
||||
instance_type = "n2-standard-2"
|
||||
service_account = {
|
||||
auto_create = true
|
||||
# email = module.compute-sa.email
|
||||
scopes = [
|
||||
"https://www.googleapis.com/auth/devstorage.read_only",
|
||||
"https://www.googleapis.com/auth/logging.write",
|
||||
"https://www.googleapis.com/auth/monitoring.write",
|
||||
"https://www.googleapis.com/auth/cloud-platform"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
apiVersion: storage.k8s.io/v1
|
||||
kind: StorageClass
|
||||
metadata:
|
||||
name: fast-storageclass
|
||||
provisioner: pd.csi.storage.gke.io
|
||||
volumeBindingMode: WaitForFirstConsumer
|
||||
reclaimPolicy: Retain
|
||||
allowVolumeExpansion: true
|
||||
parameters:
|
||||
type: pd-balanced
|
|
@ -0,0 +1,26 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
# TODO: Should terraform generate the secret?
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: mysql-secret
|
||||
namespace: "${namespace}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
rootUser: root
|
||||
rootHost: '%'
|
||||
rootPassword: ${mysql_password}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mysql-router
|
||||
namespace: "${namespace}"
|
||||
labels:
|
||||
app: mysql-router
|
||||
annotations:
|
||||
networking.gke.io/load-balancer-type: "Internal"
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
%{ if mysql_config.ip_address != null }
|
||||
loadBalancerIP: "${mysql_config.ip_address}"
|
||||
%{ endif }
|
||||
selector:
|
||||
component: mysqlrouter
|
||||
mysql.oracle.com/cluster: mycluster
|
||||
tier: mysql
|
||||
ports:
|
||||
- name: mysql-rw
|
||||
port: 6446
|
||||
- name: mysql-ro
|
||||
port: 6447
|
||||
- name: mysqlx-rw
|
||||
port: 64460
|
||||
- name: mysqlx-ro
|
||||
port: 64470
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
apiVersion: mysql.oracle.com/v2
|
||||
kind: InnoDBCluster
|
||||
metadata:
|
||||
name: mycluster
|
||||
spec:
|
||||
secretName: mysql-secret
|
||||
tlsUseSelfSigned: true
|
||||
instances: ${mysql_config.db_replicas}
|
||||
version: ${mysql_config.version}
|
||||
router:
|
||||
instances: ${mysql_config.router_replicas}
|
||||
version: ${mysql_config.version}
|
||||
podSpec:
|
||||
containers:
|
||||
- name: router
|
||||
resources:
|
||||
limits:
|
||||
cpu: "${mysql_config.router_cpu}"
|
||||
memory: "${mysql_config.router_memory}"
|
||||
requests:
|
||||
cpu: "${mysql_config.router_cpu}"
|
||||
memory: "${mysql_config.router_memory}"
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchLabels:
|
||||
component: mysqlrouter
|
||||
mysql.oracle.com/cluster: mycluster
|
||||
tier: mysql
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
# matchExpressions:
|
||||
# - key: component
|
||||
# operator: In
|
||||
# values:
|
||||
# - mysqlrouter
|
||||
# - key: mysql.oracle.com/cluster
|
||||
# operator: In
|
||||
# values:
|
||||
# - mycluster
|
||||
# - key: tier
|
||||
# operator: In
|
||||
# values:
|
||||
# - mysql
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
minDomains: ${min(3, mysql_config.router_replicas)} # ensure that at least three different zones are in use
|
||||
topologyKey: topology.kubernetes.io/zone
|
||||
whenUnsatisfiable: DoNotSchedule
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
component: mysqlrouter
|
||||
mysql.oracle.com/cluster: mycluster
|
||||
tier: mysql
|
||||
datadirVolumeClaimTemplate:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: fast-storageclass
|
||||
resources:
|
||||
requests:
|
||||
storage: ${mysql_config.db_database_size}
|
||||
# imageRepository: ${registry_path} # can we proxy to: container-registry.oracle.com/mysql ?
|
||||
podSpec:
|
||||
containers:
|
||||
- name: mysql
|
||||
resources:
|
||||
limits:
|
||||
cpu: "${mysql_config.db_cpu}"
|
||||
memory: "${mysql_config.db_memory}"
|
||||
requests:
|
||||
cpu: "${mysql_config.db_cpu}"
|
||||
memory: "${mysql_config.db_memory}"
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
minDomains: ${min(3, mysql_config.db_replicas)} # ensure that at least three different zones are in use
|
||||
topologyKey: topology.kubernetes.io/zone
|
||||
whenUnsatisfiable: DoNotSchedule
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
component: mysqld
|
||||
mysql.oracle.com/cluster: mycluster
|
||||
tier: mysql
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchLabels:
|
||||
component: mysqld
|
||||
mysql.oracle.com/cluster: mycluster
|
||||
tier: mysql
|
||||
topologyKey: kubernetes.io/hostname
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
|
||||
output "mysql_password" {
|
||||
description = "Password for the MySQL root user."
|
||||
value = random_password.mysql_password.result
|
||||
sensitive = true
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
data "google_client_config" "identity" {
|
||||
count = var.credentials_config.fleet_host != null ? 1 : 0
|
||||
}
|
||||
|
||||
provider "kubernetes" {
|
||||
config_path = (
|
||||
var.credentials_config.kubeconfig == null
|
||||
? null
|
||||
: pathexpand(var.credentials_config.kubeconfig.path)
|
||||
)
|
||||
config_context = try(
|
||||
var.credentials_config.kubeconfig.context, null
|
||||
)
|
||||
host = (
|
||||
var.credentials_config.fleet_host == null
|
||||
? null
|
||||
: var.credentials_config.fleet_host
|
||||
)
|
||||
token = try(data.google_client_config.identity.0.access_token, null)
|
||||
}
|
||||
|
||||
provider "kubectl" {
|
||||
host = (
|
||||
var.credentials_config.fleet_host == null
|
||||
? null
|
||||
: var.credentials_config.fleet_host
|
||||
)
|
||||
config_path = (
|
||||
var.credentials_config.kubeconfig == null
|
||||
? null
|
||||
: pathexpand(var.credentials_config.kubeconfig.path)
|
||||
)
|
||||
token = try(data.google_client_config.identity.0.access_token, null)
|
||||
}
|
||||
|
||||
provider "helm" {
|
||||
kubernetes {
|
||||
config_path = (
|
||||
var.credentials_config.kubeconfig == null
|
||||
? null
|
||||
: pathexpand(var.credentials_config.kubeconfig.path)
|
||||
)
|
||||
config_context = try(
|
||||
var.credentials_config.kubeconfig.context, null
|
||||
)
|
||||
host = (
|
||||
var.credentials_config.fleet_host == null
|
||||
? null
|
||||
: var.credentials_config.fleet_host
|
||||
)
|
||||
token = try(data.google_client_config.identity.0.access_token, null)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
# Deploying a highly available MySQL cluster on top of Google Kubernetes Engine
|
||||
<walkthrough-tutorial-duration duration="40"></walkthrough-tutorial-duration>
|
||||
|
||||
## Let's get started!
|
||||
This guide will show you how to deploy MySQL highly available cluster on top Google Kubernetes Engine. The uses 3 MySQL instances behind MySQL-proxy which is responsible to route traffic to active instance.
|
||||
|
||||
During this guide you will deploy a new GKE cluster, MySQL database and you will connect to database to check its connectivity.
|
||||
|
||||
**Time to complete**: About TBC minutes
|
||||
|
||||
**Prerequisites**: A GCP Project with billing enabled
|
||||
|
||||
Click the **Start** button to move to the next step.
|
||||
|
||||
|
||||
## Create or select a project
|
||||
<walkthrough-project-setup billing="true"></walkthrough-project-setup>
|
||||
|
||||
|
||||
## Create GKE autopilot cluster
|
||||
1. Create a new `terraform.tfvars` file
|
||||
```sh
|
||||
touch autopilot-cluster/terraform.tfvars
|
||||
```
|
||||
|
||||
2. Open <walkthrough-editor-open-file filePath="autopilot-cluster/terraform.tfvars">autopilot-cluster/terraform.tfvars</walkthrough-editor-open-file> file.
|
||||
|
||||
3. Paste the following content into the file and adapt for your needs if necessary
|
||||
```tfvars
|
||||
project_id = "<walkthrough-project-id/>"
|
||||
cluster_name = "cluster-00"
|
||||
cluster_create = {
|
||||
deletion_protection = false
|
||||
}
|
||||
region = "europe-west4"
|
||||
vpc_create = {
|
||||
enable_cloud_nat = true
|
||||
}
|
||||
```
|
||||
MySQL cluster images are downloaded from Oracle repository, thus cluster network requires Internet connectivity. This is provided by provisioning Cloud NAT instance.
|
||||
|
||||
4. Initialize terraform
|
||||
```sh
|
||||
cd autopilot-cluster/
|
||||
terraform init
|
||||
```
|
||||
|
||||
5. Deploy GKE Autopilot cluster
|
||||
```sh
|
||||
terraform apply
|
||||
````
|
||||
|
||||
Once finished successfully (this should take around 10 minutes) you should see following output at the end:
|
||||
```terminal
|
||||
Apply complete! Resources: 26 added, 0 changed, 0 destroyed.
|
||||
|
||||
Outputs:
|
||||
|
||||
created_resources = {
|
||||
"cloud_nat" = "projects/wns-gke-cloudshell/regions/europe-west8/routers/default-nat"
|
||||
"cluster" = "projects/wns-gke-cloudshell/locations/europe-west8/clusters/cluster-00"
|
||||
"node_service_account" = "jump-0@<project-id>.gserviceaccount.com"
|
||||
"registry" = "europe-west8-docker.pkg.dev/<project-id>/jump-0"
|
||||
"router" = "<project-id>/europe-west8/default-nat/default"
|
||||
"subnet_id" = "projects/<project-id>/regions/europe-west8/subnetworks/jump-0-default"
|
||||
"vpc_id" = "projects/<project-id>/global/networks/jump-0"
|
||||
}
|
||||
credentials_config = {
|
||||
"fleet_host" = "https://connectgateway.googleapis.com/v1/projects/<project-number>/locations/global/gkeMemberships/cluster-00"
|
||||
}
|
||||
fleet_host = "https://connectgateway.googleapis.com/v1/projects/<project-number>/locations/global/gkeMemberships/cluster-00"
|
||||
get_credentials = {
|
||||
"direct" = "gcloud container clusters get-credentials cluster-00 --project <project-id> --location europe-west8"
|
||||
"fleet" = "gcloud container fleet memberships get-credentials cluster-00 --project <project-id>"
|
||||
}
|
||||
region = "europe-west4"
|
||||
```
|
||||
|
||||
## Prepare configuration for MySQL deployment
|
||||
To deploy MySQL you need to provide a few references to already created GKE cluster. The module used provides outputs
|
||||
which helps to create those references.
|
||||
|
||||
1. Change directory to mysql
|
||||
```sh
|
||||
cd ../mysql
|
||||
```
|
||||
2. Create a new `terraform.tfvars` for MySQL deployment
|
||||
```sh
|
||||
touch terraform.tfvars
|
||||
```
|
||||
|
||||
3. Open <walkthrough-editor-open-file filePath="mysql/terraform.tfvars">mysql/terraform.tfvars</walkthrough-editor-open-file> file.
|
||||
|
||||
4. Paste the following content into the file and adapt for your needs if necessary
|
||||
```tfvars
|
||||
created_resources = {
|
||||
vpc_id = "jump-0"
|
||||
subnet_id = "projects/<walkthrough-project-id/>/regions/europe-west4/subnetworks/jump-0-default"
|
||||
}
|
||||
credentials_config = {
|
||||
kubeconfig = {
|
||||
path = "~/.kube/config"
|
||||
}
|
||||
}
|
||||
mysql_config = {
|
||||
ip_address = "10.0.0.20"
|
||||
# db_cpu = "500m"
|
||||
# db_database_size = "10Gi"
|
||||
# db_memory = "1Gi"
|
||||
# db_replicas = 3
|
||||
# router_replicas = 2 # cannot be higher than number of the zones in region
|
||||
# router_cpu = "500m"
|
||||
# router_memory = "2Gi"
|
||||
# version = "8.0.34"
|
||||
}
|
||||
namespace = "mysql-ha"
|
||||
project_id = "<walkthrough-project-id/>"
|
||||
region = "europe-west4" # use the same region as for autopilot-cluster
|
||||
```
|
||||
|
||||
5. Get credentials for created cluster
|
||||
```sh
|
||||
gcloud container fleet memberships get-credentials cluster-00 --project <walkthrough-project-id/>
|
||||
```
|
||||
|
||||
## Deploy
|
||||
1. Initialize terraform
|
||||
```sh
|
||||
terraform init
|
||||
```
|
||||
|
||||
2. Deploy MySQL
|
||||
```sh
|
||||
terraform apply
|
||||
````
|
||||
|
||||
3. Wait until deployment is ready
|
||||
```sh
|
||||
kubectl get statefulset -n mysql-ha mycluster -w
|
||||
```
|
||||
You should see following response, when all nodes are ready:
|
||||
```terminal
|
||||
NAME READY AGE
|
||||
mycluster 0/3 118s
|
||||
mycluster 1/3 3m39s
|
||||
mycluster 2/3 3m54s
|
||||
mycluster 3/3 4m5s
|
||||
```
|
||||
It takes 4-5 minutes time, as cluster needs to create new nodes to accommodate this workload. Once all nodes are ready, press ctrl-c to return to the command line.
|
||||
|
||||
## Check connectivity
|
||||
1. Get password to MySQL user
|
||||
```sh
|
||||
echo $(terraform output -raw mysql_password)
|
||||
```
|
||||
|
||||
2. Login to bastion host:
|
||||
```sh
|
||||
gcloud compute ssh --project <walkthrough-project-id/> bastion
|
||||
```
|
||||
|
||||
3. Install mysql-client:
|
||||
```sh
|
||||
sudo apt update
|
||||
sudo apt install -y mariadb-client
|
||||
```
|
||||
|
||||
4. Connect to database
|
||||
```sh
|
||||
mysql --ssl -h 10.0.0.20 -P 6446 -u root -p
|
||||
```
|
||||
And paste copied password.
|
||||
|
||||
5. Create a sample table
|
||||
```sh
|
||||
use mysql
|
||||
create table ha_test(id INT NOT NULL AUTO_INCREMENT, data varchar(64), PRIMARY KEY (id));
|
||||
insert into ha_test(data) values('123');
|
||||
select * from ha_test;
|
||||
```
|
||||
|
||||
## Congratulations
|
||||
<walkthrough-conclusion-trophy></walkthrough-conclusion-trophy>
|
||||
|
||||
You're all set!
|
||||
|
||||
**Don't forget to clean up after yourself**: If you created test projects, be sure to delete them to avoid unnecessary charges. Use `gcloud projects delete <PROJECT-ID>`.
|
||||
To remove MySQL resources:
|
||||
```sh
|
||||
terraform destroy
|
||||
```
|
||||
|
||||
And then remove the cluster:
|
||||
```sh
|
||||
cd ../autopilot-cluster
|
||||
terraform destroy
|
||||
```
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
variable "created_resources" {
|
||||
description = "IDs of the resources created by autopilot cluster to be consumed here."
|
||||
type = object({
|
||||
vpc_id = string
|
||||
subnet_id = string
|
||||
})
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "credentials_config" {
|
||||
description = "Configure how Terraform authenticates to the cluster."
|
||||
type = object({
|
||||
fleet_host = optional(string)
|
||||
kubeconfig = optional(object({
|
||||
context = optional(string)
|
||||
path = optional(string, "~/.kube/config")
|
||||
}))
|
||||
})
|
||||
nullable = false
|
||||
validation {
|
||||
condition = (
|
||||
(var.credentials_config.fleet_host != null) !=
|
||||
(var.credentials_config.kubeconfig != null)
|
||||
)
|
||||
error_message = "Exactly one of fleet host or kubeconfig must be set."
|
||||
}
|
||||
}
|
||||
|
||||
variable "mysql_config" {
|
||||
description = "Configure MySQL server and router instances."
|
||||
type = object({
|
||||
db_cpu = optional(string, "500m")
|
||||
db_database_size = optional(string, "10Gi")
|
||||
db_memory = optional(string, "1Gi")
|
||||
db_replicas = optional(number, 3)
|
||||
ip_address = optional(string)
|
||||
router_replicas = optional(number, 2) # cannot be higher than number of the zones in region
|
||||
router_cpu = optional(string, "500m")
|
||||
router_memory = optional(string, "2Gi")
|
||||
version = optional(string, "8.0.34") # latest is 8.0.34, originally was with 8.0.28 / 8.0.27,
|
||||
})
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "namespace" {
|
||||
description = "Namespace used for MySQL cluster resources."
|
||||
type = string
|
||||
nullable = false
|
||||
default = "mysql1"
|
||||
}
|
||||
|
||||
variable "project_id" {
|
||||
description = "Project to deploy bastion host."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "Region used for cluster and network resources."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "registry_path" {
|
||||
description = "Repository path for images. Default is to use Docker Hub images."
|
||||
type = string
|
||||
nullable = false
|
||||
default = "docker.io"
|
||||
}
|
||||
|
||||
variable "templates_path" {
|
||||
description = "Path where manifest templates will be read from. Set to null to use the default manifests."
|
||||
type = string
|
||||
default = null
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.7.0"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 5.11.0, < 6.0.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 5.11.0, < 6.0.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
|
||||
terraform {
|
||||
required_providers {
|
||||
kubectl = {
|
||||
source = "gavinbunney/kubectl"
|
||||
version = ">= 1.7.0"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue