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:
Wiktor Niesiobędzki 2024-02-09 11:23:35 +01:00 committed by GitHub
parent 50c7d3c0e9
commit 597579fa2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 836 additions and 0 deletions

View File

@ -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&#40;&#123;&#10; vpc_id &#61; string&#10; subnet_id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [credentials_config](variables.tf#L26) | Configure how Terraform authenticates to the cluster. | <code title="object&#40;&#123;&#10; fleet_host &#61; optional&#40;string&#41;&#10; kubeconfig &#61; optional&#40;object&#40;&#123;&#10; context &#61; optional&#40;string&#41;&#10; path &#61; optional&#40;string, &#34;&#126;&#47;.kube&#47;config&#34;&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</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&#40;&#123;&#10; db_cpu &#61; optional&#40;string, &#34;500m&#34;&#41;&#10; db_database_size &#61; optional&#40;string, &#34;10Gi&#34;&#41;&#10; db_memory &#61; optional&#40;string, &#34;1Gi&#34;&#41;&#10; db_replicas &#61; optional&#40;number, 3&#41;&#10; ip_address &#61; optional&#40;string&#41;&#10; router_replicas &#61; optional&#40;number, 2&#41; &#35; cannot be higher than number of the zones in region&#10; router_cpu &#61; optional&#40;string, &#34;500m&#34;&#41;&#10; router_memory &#61; optional&#40;string, &#34;2Gi&#34;&#41;&#10; version &#61; optional&#40;string, &#34;8.0.34&#34;&#41; &#35; latest is 8.0.34, originally was with 8.0.28 &#47; 8.0.27,&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [namespace](variables.tf#L62) | Namespace used for MySQL cluster resources. | <code>string</code> | | <code>&#34;mysql1&#34;</code> |
| [registry_path](variables.tf#L79) | Repository path for images. Default is to use Docker Hub images. | <code>string</code> | | <code>&#34;docker.io&#34;</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 -->

View File

@ -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"
]
}
}

View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
```

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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"
}
}
}