diff --git a/CODEOWNERS b/CODEOWNERS index 95d40b04a..0b1882cc2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -30,6 +30,7 @@ /tools/apigee-openlegacy @tomfi @joelgauci /tools/apigee-sackmesser @danistrebel /tools/apigee-x-trial-provision @yuriylesyuk @danistrebel +/tools/apigee-hybrid-terraform @rajeshmi @ayos /tools/decrypt-hybrid-assets @yuriylesyuk /tools/endpoints-oas-importer @danistrebel /tools/grpc-http-gateway-generator @danistrebel @omidtahouri @@ -39,4 +40,4 @@ /tools/pipeline-runner @seymen @danistrebel /tools/proxy-endpoint-unifier @anaik91 /tools/sf-dependency-list @yuriylesyuk -/tools/target-server-validator @anaik91 \ No newline at end of file +/tools/target-server-validator @anaik91 diff --git a/README.md b/README.md index 877015d14..d431d3ea9 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ Apigee products. A tool to build and test groups of Apigee projects - [Pipeline Linter](tools/pipeline-linter) - A tool to lint groups of Apigee projects +- [Apigee hybrid Terraform Script](tools/apigee-hybrid-terraform) - + Apigee Hybrid Terraform script to deploy Apigee hybrid on GKE,AKS,EKS and Bring Your Own Cluster - [Apigee hybrid Quickstart GKE](tools/hybrid-quickstart) - A quickstart setup configuration for Apigee hybrid on GKE - [Decrypt Hybrid Assets](tools/decrypt-hybrid-assets) - diff --git a/tools/apigee-hybrid-terraform/.flake8 b/tools/apigee-hybrid-terraform/.flake8 new file mode 100644 index 000000000..c48904889 --- /dev/null +++ b/tools/apigee-hybrid-terraform/.flake8 @@ -0,0 +1,11 @@ +[flake8] +# Ignore the following error codes: +# F401: 'X' imported but unused +# E128: continuation line under-indented for visual indent +# E303: too many blank lines (2) +# W291: trailing whitespace +# W292: no newline at end of file +# W293: blank line contains whitespace +# W391: blank line at end of file +ignore = F401, E128, E303, W291, W292, W293, W391 +max-line-length = 120 diff --git a/tools/apigee-hybrid-terraform/.gitignore b/tools/apigee-hybrid-terraform/.gitignore new file mode 100644 index 000000000..342cb0046 --- /dev/null +++ b/tools/apigee-hybrid-terraform/.gitignore @@ -0,0 +1,42 @@ +# Local .terraform directories +.terraform/ +.terraform.lock.hcl + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +#*.tfvars +#*.tfvars.json + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Ignore transient lock info files created by terraform apply +.terraform.tfstate.lock.info + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc +new_policy.yaml +output +kubeconfig +net_policy.yaml \ No newline at end of file diff --git a/tools/apigee-hybrid-terraform/LICENSE b/tools/apigee-hybrid-terraform/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/tools/apigee-hybrid-terraform/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/tools/apigee-hybrid-terraform/README.md b/tools/apigee-hybrid-terraform/README.md new file mode 100644 index 000000000..dd491e81f --- /dev/null +++ b/tools/apigee-hybrid-terraform/README.md @@ -0,0 +1,244 @@ +# Apigee Hybrid Terraform + +This repository contains Terraform configurations for deploying and managing Apigee Hybrid. The project supports deployment on multiple Kubernetes platforms including Google Kubernetes Engine (GKE), Azure Kubernetes Service (AKS), Elastic Kubernetes Service (EKS), and other supported Kubernetes platforms. This setup is ideal for creating an evaluation Apigee instance to test features and functionality. + +## Project Structure + +``` +├── apigee-hybrid-core/ # Core Apigee Hybrid infrastructure components +├── apigee-on-aks/ # AKS-specific deployment configurations +├── apigee-on-gke/ # GKE-specific deployment configurations +├── apigee-on-eks/ # EKS-specific deployment configurations +├── apigee-on-others/ # Install Apigee on other Kubernetes Provider/ +``` + +## Prerequisites + +### Required Tools +- Terraform >= 1.0.0 +- Google Cloud SDK (gcloud CLI) >= 400.0.0 +- kubectl >= 1.24.0 +- Helm >= 3.15.0 + +### GCP Project Setup +- A GCP project with billing enabled +- Appropriate IAM permissions (Owner/Editor role) +- Required APIs enabled (handled automatically by Terraform): + - Compute Engine API + - Container (GKE) API + - Cloud Resource Manager API + - Apigee API + - Apigee Connect API + - Cloud KMS API + - Service Networking API + - Cloud Monitoring API + - Cloud Logging API + - Cloud Storage API + - Cloud SQL Admin API + +### Organization Policies +The following organization policies should use Google's default settings: +- `disableServiceAccountKeyCreation` +- `requireOsLogin` +- `requireShieldedVm` +- `vmExternalIpAccess` + +To apply these policies, run: +```bash +./apply_org_policies.sh +``` + +## Quick Start + +1. Clone this repository: + ```bash + git clone https://github.com/your-username/apigee-hybrid-terraform.git + cd apigee-hybrid-terraform + ``` + +2. Choose your deployment target: + - For GKE deployment: Navigate to `apigee-on-gke/` + - For AKS deployment: Navigate to `apigee-on-aks/` + - For EKS deployment: Navigate to `apigee-on-eks/` + - For other Kubernetes Provider deployment: Navigate to `apigee-on-others/` + +3. Initialize Terraform: + ```bash + terraform init + ``` + +4. Configure your variables: + - Edit `terraform.tfvars` with required values. You can refer `terraform.tfvars.sample` + - Update the variables with your specific values + +5. Apply the configuration: + ```bash + terraform plan + terraform apply + ``` + +6. Verify the deployment: + ```bash + kubectl get pods -n apigee + ``` + +## Components + +### Core Infrastructure (`apigee-hybrid-core/`) + +The core module provides the fundamental infrastructure components required for Apigee Hybrid, including: +- IAM configurations +- Service accounts +- Core GCP resources + +### GKE Deployment ([`apigee-on-gke/`](apigee-on-gke/)) + +Specific configurations for deploying Apigee Hybrid on Google Kubernetes Engine, including: +- GKE cluster configuration +- Apigee runtime components +- Network configurations +- Load balancer setup + +### AKS Deployment ([`apigee-on-aks/`](apigee-on-aks/)) + +Configurations for deploying Apigee Hybrid on Azure Kubernetes Service, including: +- AKS cluster setup +- Network configurations +- Load balancer setup +- Apigee Runtime Installation + + +### EKS Deployment ([`apigee-on-eks/`](apigee-on-eks/)) + +Configurations for deploying Apigee Hybrid on AWS Kubernetes Service, including: +- EKS cluster setup +- Network configurations +- Load balancer setup +- Apigee Runtime Installation + +### Other K8s Deployment ([`apigee-on-others/`](apigee-on-others/)) + +Configurations for deploying Apigee Hybrid on other Kubernetes Service, including: + +- Apigee Runtime Installation + +## Maintenance + +### Upgrading + +1. Review the release notes for the target version +2. Update the Apigee runtime version in your configuration +3. Apply the changes using Terraform: + ```bash + terraform plan + terraform apply + ``` +4. Verify the upgrade: + ```bash + kubectl get pods -n apigee + ``` + +### Backup and Recovery + +- Regular backups of the Apigee runtime data +- Terraform state backup +- Configuration version control +- Disaster recovery procedures + +### Health Checks + +Regular health checks should be performed: +```bash +kubectl get pods -n apigee +kubectl get services -n apigee +kubectl describe pods -n apigee +``` + +## Known Issues and Solutions + +### Terraform Provider Warnings + +1. **Deprecated `local_file` Resource** + ``` + Warning: Attribute Deprecated + Use the `local_sensitive_file` resource instead + ``` + - **Solution**: Update the code to use `local_sensitive_file` instead of `local_file` for sensitive content + - **Location**: `apigee-hybrid-core/main.tf` + +2. **Deprecated `inline_policy` in AWS IAM Role** + ``` + Warning: Argument is deprecated + inline_policy is deprecated. Use the aws_iam_role_policy resource instead + ``` + - **Solution**: Replace `inline_policy` with separate `aws_iam_role_policy` resources + - **Location**: EKS module configuration + +### Provider Inconsistencies + +1. **Google Service Account Inconsistency** + ``` + Error: Provider produced inconsistent result after apply + When applying changes to module.apigee_hybrid.google_service_account.apigee_non_prod_sa + ``` + - **Solution**: + 1. Remove the service account from GCP Project + 2. Reapply the terraform configuration 'terraform apply' + +### Common Issues + +1. **Cluster Creation Fails** + - Check IAM permissions + - Verify quota availability + - Review network configurations + - Check resource limits + +2. **Apigee Runtime Issues** + - Check pod status: `kubectl get pods -n apigee` + - Review logs: `kubectl logs -n apigee` + - Verify connectivity to Apigee control plane + - Check resource constraints + +3. **Network Connectivity Issues** + - Verify VPC configurations + - Check firewall rules + - Validate DNS settings + - Review load balancer configuration + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Commit your changes +4. Push to the branch +5. Create a Pull Request + +### Development Guidelines +- Follow Terraform best practices +- Include documentation for new features +- Add tests for new functionality +- Update version numbers appropriately + +## License + +This project is licensed under the terms of the license included in the repository. + +## Support + +For issues and feature requests, please create an issue in the GitHub repository. + +### Getting Help +- Check the [FAQ](docs/FAQ.md) +- Review the [troubleshooting guide](docs/TROUBLESHOOTING.md) +- Join the community Slack channel +- Contact the maintainers + +## Additional Resources + +- [Apigee Hybrid Documentation](https://cloud.google.com/apigee/docs/hybrid) +- [Terraform Documentation](https://www.terraform.io/docs) +- [GKE Documentation](https://cloud.google.com/kubernetes-engine/docs) +- [AKS Documentation](https://docs.microsoft.com/azure/aks) +- [EKS Documentation](https://docs.aws.amazon.com/eks) +- [Kubernetes Documentation](https://kubernetes.io/docs) +- [Helm Documentation](https://helm.sh/docs) diff --git a/tools/apigee-hybrid-terraform/apigee-hybrid-core/.gitignore b/tools/apigee-hybrid-terraform/apigee-hybrid-core/.gitignore new file mode 100644 index 000000000..6665869f8 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-hybrid-core/.gitignore @@ -0,0 +1,27 @@ +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* +*.tfplan + +# Crash log files +crash.log + +# Exclude all .tfvars files, which are likely to contain sentitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Ignore CLI configuration files +.terraformrc +terraform.rc diff --git a/tools/apigee-hybrid-terraform/apigee-hybrid-core/README.md b/tools/apigee-hybrid-terraform/apigee-hybrid-core/README.md new file mode 100644 index 000000000..d348ea4cf --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-hybrid-core/README.md @@ -0,0 +1,123 @@ + +## Apigee Hybrid Setup Script + +The `setup_apigee.sh` script automates the installation and configuration of Apigee Hybrid. It handles the deployment of all necessary components including the operator, datastore, telemetry, and ingress configurations. + +### Prerequisites + +Before running the script, ensure you have: + +1. Access to a running Kubernetes cluster +2. Required files: + - `overrides.yaml`: Apigee configuration overrides + - `service.yaml`: Service template configuration + - Service account key JSON file + - TLS certificate and private key for environment group +3. Required tools: + - `kubectl` configured to access your Kubernetes cluster + - `helm` (version 3.10+) + - `gcloud` CLI configured with appropriate permissions + +### Script Parameters + +The script accepts the following parameters: + +| Parameter | Short | Required | Default | Description | +|-----------|-------|----------|---------|-------------| +| `--version` | `-v` | No | 1.14.2-hotfix.1 | Apigee version to install | +| `--namespace` | `-n` | No | apigee | Kubernetes namespace for Apigee components | +| `--kubeconfig` | `-f` | No | - | Kubernetes Config File | +| `--sa_email` | `-a` | Yes | - | Service Account email | +| `--overrides` | `-o` | Yes | - | Path to overrides.yaml file | +| `--service` | `-s` | Yes | - | Path to service template file | +| `--key` | `-k` | Yes | - | Path to service account key JSON file | +| `--cert` | `-c` | Yes | - | Path to environment group certificate file | +| `--private-key` | `-p` | Yes | - | Path to environment group private key file | +| `--help` | `-h` | No | - | Display help message | + +### Sample Usage + + +1. **Custom Version and Namespace**: + ```bash + ./setup_apigee.sh \ + --version "1.14.2-hotfix.1" \ + --namespace "apigee-prod" \ + --kubeconfig "/path/to/kubeconfig" \ + --sa_email "apigee-svc-tf@project.gserviceaccount.com" \ + --overrides "/path/to/overrides.yaml" \ + --service "/path/to/service.yaml" \ + --key "/path/to/sa-key.json" \ + --cert "/path/to/cert.pem" \ + --private-key "/path/to/key.pem" + ``` + +2. **Using Short Options**: + ```bash + ./setup_apigee.sh \ + -v "1.14.2-hotfix.1" \ + -n "apigee" \ + -f "/path/to/kubeconfig" \ + -a "apigee-svc-tf@project.gserviceaccount.com" \ + -o "/path/to/overrides.yaml" \ + -s "/path/to/service.yaml" \ + -k "/path/to/sa-key.json" \ + -c "/path/to/cert.pem" \ + -p "/path/to/key.pem" + ``` + +### What the Script Does + +1. **Setup Phase**: + - Creates necessary directories + - Pulls required Helm charts + - Copies configuration files to appropriate locations + +2. **Installation Phase**: + - Creates Kubernetes namespace + - Enables control plane access + - Installs CRDs + - Installs cert-manager + - Installs Apigee operator + - Installs datastore + - Installs telemetry + - Installs Redis + - Installs ingress manager + - Installs organization + - Installs environment + - Installs environment group + - Sets up ingress + +### Troubleshooting + +If you encounter issues: + +1. **Check Prerequisites**: + ```bash + # Verify kubectl access + kubectl cluster-info + + # Verify helm version + helm version + + # Verify gcloud configuration + gcloud config list + ``` + +2. **Check Logs**: + ```bash + # Check operator logs + kubectl logs -n apigee -l app=apigee-controller + + # Check datastore logs + kubectl logs -n apigee -l app=apigee-datastore + ``` + +3. **Verify Installation**: + ```bash + # Check all pods + kubectl get pods -n apigee + + # Check services + kubectl get services -n apigee + ``` \ No newline at end of file diff --git a/tools/apigee-hybrid-terraform/apigee-hybrid-core/apigee-service-template.yaml b/tools/apigee-hybrid-terraform/apigee-hybrid-core/apigee-service-template.yaml new file mode 100644 index 000000000..3347ecf94 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-hybrid-core/apigee-service-template.yaml @@ -0,0 +1,37 @@ +#!/bin/bash + +# Copyright 2022 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: ${service_name} + namespace: ${apigee_namespace} +spec: + ports: + - name: status-port + port: 15021 + protocol: TCP + targetPort: 15021 + - name: https + port: 443 + protocol: TCP + targetPort: 8443 + selector: + app: apigee-ingressgateway # required + ingress_name: ${ingress_name} + org: ${org_name} + type: LoadBalancer + loadBalancerIP: ${apigee_lb_ip} diff --git a/tools/apigee-hybrid-terraform/apigee-hybrid-core/main.tf b/tools/apigee-hybrid-terraform/apigee-hybrid-core/main.tf new file mode 100644 index 000000000..3022f6acb --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-hybrid-core/main.tf @@ -0,0 +1,409 @@ +#!/bin/bash + +# Copyright 2022 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. + + +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> 6.30.0" # Consider pinning to a specific minor like "~> 4.80" + } + local = { + source = "hashicorp/local" + version = "~> 2.4.0" + } + tls = { + source = "hashicorp/tls" + version = "~> 4.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.5" + } + } +} + +# Generate random string for resource names +resource "random_string" "suffix" { + length = 6 # Shortened to avoid hitting length limits on some Azure resources + special = false + upper = false +} + +locals { + random_suffix = substr(random_string.suffix.result, 0, 6) + apigee_org_constructed_id = "organizations/${var.project_id}" # Apigee Org ID is the Project ID + service_account_id_short = var.apigee_service_account_name #"apigee-non-prod-${local.random_suffix}" # Or make this configurable + service_account_email = "${local.service_account_id_short}@${var.project_id}.iam.gserviceaccount.com" + + effective_org_id = var.create_org ? ( + google_apigee_organization.apigee_org[0].id + ) : local.apigee_org_constructed_id + + effective_env_name = var.apigee_env_name + effective_instance_name = var.apigee_instance_name + effective_envgroup_hostnames = var.apigee_envgroup_hostnames + effective_envgroup_name = var.apigee_envgroup_name + + effective_envgroup_id = var.create_org ? ( + google_apigee_envgroup.hybrid_envgroup[0].id + ) : (local.effective_org_id != null ? "${local.effective_org_id}/envgroups/${var.apigee_envgroup_name}" : null) + + apigee_non_prod_sa_roles = [ + "roles/storage.objectAdmin", + "roles/logging.logWriter", + "roles/apigeeconnect.Agent", + "roles/monitoring.metricWriter", + "roles/apigee.synchronizerManager", + "roles/apigee.analyticsAgent", + "roles/apigee.runtimeAgent", + ] + + primary_hostname_for_cert = length(var.apigee_envgroup_hostnames) > 0 ? var.apigee_envgroup_hostnames[0] : "default-apigee-host.example.com" + cert_filename_prefix = replace(local.primary_hostname_for_cert, ".", "-") + + output_dir = "output/${var.project_id}" # Centralize output directory path + + sa_key_filename_for_overrides = basename(local_file.apigee_non_prod_sa_key_file.filename) + cert_file_path_for_overrides = basename(local_file.apigee_envgroup_cert_file.filename) + private_key_file_path_for_overrides = basename(local_file.apigee_envgroup_private_key_file.filename) + + ingress_svc_annotations_yaml = length(var.ingress_svc_annotations) > 0 ? yamlencode(var.ingress_svc_annotations) : "" + + # Use module path for templates if specific paths aren't provided + final_overrides_template_path = var.overrides_template_path != "" ? var.overrides_template_path : "${path.module}/overrides-templates.yaml" + final_service_template_path = var.service_template_path != "" ? var.service_template_path : "${path.module}/apigee-service-template.yaml" + + # Determine the org name for overrides.yaml. Default to project_id if var.apigee_org_name is not set. + # Apigee Hybrid org ID is typically the GCP project ID. + org_name_for_overrides = var.apigee_org_name != "" ? var.apigee_org_name : var.project_id +} + +# ------------------------------------------------------------------------------ +# Enable Google Cloud Services +# ------------------------------------------------------------------------------ +resource "google_project_service" "iam" { + project = var.project_id + service = "iam.googleapis.com" + disable_dependent_services = false + disable_on_destroy = false # Set to true if you want to disable on destroy +} +resource "google_project_service" "apigee" { + project = var.project_id + service = "apigee.googleapis.com" + disable_dependent_services = false + disable_on_destroy = false +} +resource "google_project_service" "compute" { + project = var.project_id + service = "compute.googleapis.com" + disable_dependent_services = false + disable_on_destroy = false +} +resource "google_project_service" "apigeeconnect" { + project = var.project_id + service = "apigeeconnect.googleapis.com" + disable_dependent_services = false + disable_on_destroy = false +} +resource "google_project_service" "container" { # Often needed for GKE, even if using AKS for runtime, Apigee might interact + project = var.project_id + service = "container.googleapis.com" + disable_dependent_services = false + disable_on_destroy = false +} +# Add other services like storage, logging, monitoring if not already enabled +resource "google_project_service" "storage" { + project = var.project_id + service = "storage.googleapis.com" + disable_dependent_services = false + disable_on_destroy = false +} +resource "google_project_service" "logging" { + project = var.project_id + service = "logging.googleapis.com" + disable_dependent_services = false + disable_on_destroy = false +} +resource "google_project_service" "monitoring" { + project = var.project_id + service = "monitoring.googleapis.com" + disable_dependent_services = false + disable_on_destroy = false +} +# ------------------------------------------------------------------------------ +# Create Apigee Non-Prod Service Account +# ------------------------------------------------------------------------------ +resource "google_service_account" "apigee_non_prod_sa" { + project = var.project_id + account_id = local.service_account_id_short + display_name = "Apigee Hybrid Non-Prod SA" + description = "Service account for Apigee Hybrid non-production workloads" + + depends_on = [ + google_project_service.iam, # Ensure IAM API is enabled + ] +} + +resource "google_project_iam_member" "apigee_non_prod_sa_bindings" { + for_each = toset(local.apigee_non_prod_sa_roles) + project = var.project_id + role = each.value + member = "serviceAccount:${google_service_account.apigee_non_prod_sa.email}" + depends_on = [ + google_service_account.apigee_non_prod_sa, + # Add dependencies on the specific services if roles grant permissions on them + google_project_service.apigee, + google_project_service.apigeeconnect, + google_project_service.storage, + google_project_service.logging, + google_project_service.monitoring, + ] +} + +resource "google_service_account_key" "apigee_non_prod_sa_key" { + service_account_id = google_service_account.apigee_non_prod_sa.name +} + +# Ensure the output directory exists +resource "null_resource" "create_output_dir" { + triggers = { + output_dir_path = local.output_dir + } + provisioner "local-exec" { + command = "mkdir -p ${local.output_dir}" + } +} + +# Save the service account key to a local file +resource "local_file" "apigee_non_prod_sa_key_file" { + sensitive_content = base64decode(google_service_account_key.apigee_non_prod_sa_key.private_key) + filename = "${local.output_dir}/${local.service_account_id_short}-sa-key.json" + file_permission = "0600" + depends_on = [ + null_resource.create_output_dir, + google_service_account_key.apigee_non_prod_sa_key, + ] +} + +# ------------------------------------------------------------------------------ +# Self-Signed TLS Certificate for Apigee Environment Group Hostnames +# ------------------------------------------------------------------------------ +resource "tls_private_key" "apigee_envgroup_key" { + count = var.tls_apigee_self_signed ? 1 : 0 + algorithm = "RSA" + rsa_bits = 2048 +} + +resource "tls_self_signed_cert" "apigee_envgroup_cert" { + count = var.tls_apigee_self_signed ? 1 : 0 + private_key_pem = tls_private_key.apigee_envgroup_key[0].private_key_pem + dns_names = var.apigee_envgroup_hostnames + + subject { + common_name = local.primary_hostname_for_cert + organization = "Apigee Hybrid Self-Signed Cert" + } + + validity_period_hours = 8760 # 1 year + early_renewal_hours = 720 # 30 days + + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth", + ] +} + +resource "local_file" "apigee_envgroup_private_key_file" { + sensitive_content = var.tls_apigee_self_signed ? tls_private_key.apigee_envgroup_key[0].private_key_pem : var.tls_apigee_key_path + filename = "${local.output_dir}/${local.cert_filename_prefix}.key" + file_permission = "0600" + depends_on = [ + null_resource.create_output_dir, + tls_private_key.apigee_envgroup_key, + ] +} + +resource "local_file" "apigee_envgroup_cert_file" { + content = var.tls_apigee_self_signed ? tls_self_signed_cert.apigee_envgroup_cert[0].cert_pem : var.tls_apigee_cert_path + filename = "${local.output_dir}/${local.cert_filename_prefix}.crt" + file_permission = "0644" + depends_on = [ + null_resource.create_output_dir, + tls_self_signed_cert.apigee_envgroup_cert, + ] +} + +# ------------------------------------------------------------------------------ +# Apigee Organization, Environment, EnvGroup, Attachment +# ------------------------------------------------------------------------------ +resource "google_apigee_organization" "apigee_org" { + count = var.create_org ? 1 : 0 + + project_id = var.project_id # The GCP project that will host the Apigee org + display_name = var.apigee_org_display_name != "" ? var.apigee_org_display_name : "Apigee Org for ${var.project_id}" + description = "Apigee Hybrid Organization managed by Terraform" + analytics_region = var.region + runtime_type = "HYBRID" + billing_type = var.billing_type + + depends_on = [ + google_project_service.apigee, + google_project_service.compute, + google_project_service.container, # Apigee may require container API for its operations + local_file.apigee_non_prod_sa_key_file, # Ensure SA Key is created before org + + ] +} + +resource "google_apigee_environment" "hybrid_env" { + count = var.create_org ? 1 : 0 + + name = local.effective_env_name + display_name = var.apigee_env_display_name + description = "Hybrid Environment for ${local.effective_env_name}" + org_id = local.effective_org_id +} + +resource "google_apigee_envgroup" "hybrid_envgroup" { + count = var.create_org ? 1 : 0 + + name = local.effective_envgroup_name + hostnames = local.effective_envgroup_hostnames + org_id = local.effective_org_id + + depends_on = [ + google_apigee_organization.apigee_org, + google_project_service.apigee, + ] +} + +resource "google_apigee_envgroup_attachment" "env_to_group_attachment" { + count = var.create_org ? 1 : 0 + + envgroup_id = google_apigee_envgroup.hybrid_envgroup[0].id + environment = google_apigee_environment.hybrid_env[0].name + + depends_on = [ + google_apigee_environment.hybrid_env, + google_apigee_envgroup.hybrid_envgroup, + ] +} +# ------------------------------------------------------------------------------ +# Generate overrides.yaml and apigee-service.yaml +# ------------------------------------------------------------------------------ + +resource "local_file" "apigee_overrides" { + content = templatefile(local.final_overrides_template_path, { + # K8S_CLUSTER_RUNNING_APIGEE_RUNTIME + instance_id = var.apigee_instance_name # This is the Apigee Instance name (google_apigee_instance.name) + apigee_namespace = var.apigee_namespace + # GCP_PROJECT_ID used for Apigee Organization + project_id = var.project_id + analytics_region = var.region + # K8S_CLUSTER_NAME where Apigee is installed + cluster_name = var.cluster_name + cluster_location = var.region # Assuming K8s cluster region is same as Apigee region for simplicity + # APIGEE_ORGANIZATION_ID + org_name = local.org_name_for_overrides # This should be the Apigee Org ID (typically project_id) + environment_name = var.apigee_env_name + cassandra_replica_count = var.apigee_cassandra_replica_count + # File paths for SA key and certs are basenames, script will handle full paths for secrets + non_prod_service_account_filepath = local.sa_key_filename_for_overrides + ingress_name = var.ingress_name + environment_group_name = var.apigee_envgroup_name + ssl_cert_path = local.cert_file_path_for_overrides + ssl_key_path = local.private_key_file_path_for_overrides + ingress_svc_annotations_yaml = local.ingress_svc_annotations_yaml # Pass the generated YAML snippet + # Add any other variables your template needs + }) + filename = "${local.output_dir}/overrides.yaml" + file_permission = "0644" + depends_on = [ + null_resource.create_output_dir, + local_file.apigee_non_prod_sa_key_file, # Ensure SA key file is written + local_file.apigee_envgroup_cert_file, # Ensure cert file is written + local_file.apigee_envgroup_private_key_file, # Ensure key file is written + ] +} + +resource "local_file" "apigee_service" { + content = templatefile(local.final_service_template_path, { + apigee_namespace = var.apigee_namespace + # APIGEE_ORGANIZATION_ID + org_name = local.org_name_for_overrides # This should be the Apigee Org ID + ingress_name = var.ingress_name + # SERVICE_NAME often maps to envgroup name or a specific service identifier + service_name = var.apigee_envgroup_name # Or another appropriate variable + apigee_lb_ip = var.apigee_lb_ip + # Add any other variables your template needs + }) + filename = "${local.output_dir}/apigee-service.yaml" + file_permission = "0644" + depends_on = [null_resource.create_output_dir] +} + +# ------------------------------------------------------------------------------ +# Execute Apigee Setup Script +# ------------------------------------------------------------------------------ +resource "null_resource" "apigee_setup_execution" { + count = var.apigee_install ? 1 : 0 + + triggers = { + apigee_version = var.apigee_version + apigee_namespace = var.apigee_namespace + kubeconfig = var.kubeconfig + apigee_overrides_yaml_content = local_file.apigee_overrides.content + apigee_service_yaml_content = local_file.apigee_service.content + apigee_sa_key_json_content = local_file.apigee_non_prod_sa_key_file.content + apigee_envgroup_cert_content = local_file.apigee_envgroup_cert_file.content + apigee_envgroup_key_content = local_file.apigee_envgroup_private_key_file.content + script_hash = filemd5("${path.module}/setup_apigee.sh") + output_dir = local.output_dir + } + + provisioner "local-exec" { + when = destroy + #command = "kubectl delete -f ${self.triggers.output_dir}/apigee-service.yaml" + command = "if [ -n \"${self.triggers.kubeconfig}\" ] && [ -f \"${self.triggers.kubeconfig}\" ]; then export KUBECONFIG=${self.triggers.kubeconfig}; fi && kubectl delete -f ${self.triggers.output_dir}/apigee-service.yaml" + on_failure = continue + } + + provisioner "local-exec" { + command = <<-EOT + bash ${path.module}/setup_apigee.sh \ + --version "${var.apigee_version}" \ + --namespace "${var.apigee_namespace}" \ + --kubeconfig "${var.kubeconfig}" \ + --sa_email "${local.service_account_email}" \ + --overrides "${abspath(local_file.apigee_overrides.filename)}" \ + --service "${abspath(local_file.apigee_service.filename)}" \ + --key "${abspath(local_file.apigee_non_prod_sa_key_file.filename)}" \ + --cert "${abspath(local_file.apigee_envgroup_cert_file.filename)}" \ + --private-key "${abspath(local_file.apigee_envgroup_private_key_file.filename)}" + EOT + } + + depends_on = [ + google_apigee_envgroup_attachment.env_to_group_attachment, + local_file.apigee_overrides, + local_file.apigee_service, + local_file.apigee_non_prod_sa_key_file, + local_file.apigee_envgroup_cert_file, + local_file.apigee_envgroup_private_key_file, + ] +} \ No newline at end of file diff --git a/tools/apigee-hybrid-terraform/apigee-hybrid-core/outputs.tf b/tools/apigee-hybrid-terraform/apigee-hybrid-core/outputs.tf new file mode 100644 index 000000000..ba56b07cb --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-hybrid-core/outputs.tf @@ -0,0 +1,65 @@ +#!/bin/bash + +# Copyright 2022 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. + +output "apigee_non_prod_sa_email" { + description = "Email of the Apigee Non-Prod service account." + value = google_service_account.apigee_non_prod_sa.email +} + +output "apigee_non_prod_sa_key_path" { + description = "Path to the saved Apigee Non-Prod service account key file." + value = local_file.apigee_non_prod_sa_key_file.filename +} + +output "apigee_overrides_yaml_path" { + description = "Path to the generated Apigee Hybrid overrides.yaml file." + value = local_file.apigee_overrides.filename +} + +output "apigee_service_yaml_path" { + description = "Path to the generated Apigee Hybrid apigee-service.yaml file." + value = local_file.apigee_service.filename +} + +output "apigee_envgroup_private_key_file_path" { + description = "Path to the self-signed private key file for the Apigee envgroup hostname(s)." + value = local_file.apigee_envgroup_private_key_file.filename +} + +output "apigee_envgroup_cert_file_path" { + description = "Path to the self-signed certificate file for the Apigee envgroup hostname(s)." + value = local_file.apigee_envgroup_cert_file.filename +} + +output "apigee_setup_script_executed_trigger" { + description = "Indicates if the Apigee setup script was triggered. This output changes if the script's triggers change." + value = var.apigee_install ? null_resource.apigee_setup_execution[0].id : "Apigee setup script was skipped (apigee_install=false)." +} + +output "apigee_organization_id" { + description = "The ID of the Apigee organization." + value = local.effective_org_id +} + +output "apigee_environment_name" { + description = "The name of the Apigee environment." + value = local.effective_env_name +} + +output "apigee_envgroup_id" { + description = "The ID of the Apigee environment group." + value = local.effective_envgroup_id +} diff --git a/tools/apigee-hybrid-terraform/apigee-hybrid-core/overrides-templates.yaml b/tools/apigee-hybrid-terraform/apigee-hybrid-core/overrides-templates.yaml new file mode 100644 index 000000000..a38924792 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-hybrid-core/overrides-templates.yaml @@ -0,0 +1,127 @@ +#!/bin/bash + +# Copyright 2022 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. + +instanceID: "${instance_id}" +namespace: ${apigee_namespace} # Usually "apigee" + +gcp: + projectID: ${project_id} + region: ${analytics_region} + +k8sCluster: + name: ${cluster_name} + # Must be the closest Google Cloud region to your cluster. + region: ${cluster_location} + +org: ${org_name} + +# Required for Enhanced per-environment proxy limits: +enhanceProxyLimits: true + +# Required if using data residency with hybrid: + +# Required for data residency with hybrid and Enhanced per-environment +# proxy limits: +newDataPipeline: + debugSession: true + analytics: true + +envs: + - name: ${environment_name} + serviceAccountPaths: + # Provide the path relative to the apigee-env chart directory. + synchronizer: ${non_prod_service_account_filepath} + # For example: "PROJECT_ID-apigee-non-prod.json" + runtime: ${non_prod_service_account_filepath} + # For example: "PROJECT_ID-apigee-non-prod.json" + udca: ${non_prod_service_account_filepath} + # For example: "PROJECT_ID-apigee-non-prod.json" + +cassandra: + hostNetwork: false + # Set to false for single region installations and multi-region + # installations with connectivity between pods in different clusters, + # for example GKE installations. + # Set to true for multi-region installations with no communication + # between pods in different clusters, for example Google Distributed + # Cloud on VMware or bare metal, GKE on AWS, AKS, EKS, and OpenShift + # installations. + # See Multi-region deployment: Prerequisites + replicaCount: ${cassandra_replica_count} + # Use 1 for non-prod or "demo" installations and multiples of 3 + # for production. + # See Configure Cassandra for production for guidelines. + + +nodeSelector: + requiredForScheduling: false + apigeeRuntime: + key: "cloud.google.com/gke-nodepool" + value: "apigee-runtime" + apigeeData: + key: "cloud.google.com/gke-nodepool" + value: "apigee-data" + + +ingressGateways: + - name: ${ingress_name} # maximum 17 characters. + replicaCountMin: 2 + replicaCountMax: 10 + svcType: ClusterIP + + +virtualhosts: + - name: ${environment_group_name} + selector: + app: apigee-ingressgateway + ingress_name: ${ingress_name} + sslCertPath: certs/${ssl_cert_path} + sslKeyPath: certs/${ssl_key_path} + +mart: + serviceAccountPath: ${non_prod_service_account_filepath} + # Provide the path relative to the chart directory. + # For example: "PROJECT_ID-apigee-non-prod.json" + +connectAgent: + serviceAccountPath: ${non_prod_service_account_filepath} + # Provide the path relative to the apigee-org chart directory. + # Use the same service account for mart and connectAgent + # For example: "PROJECT_ID-apigee-non-prod.json" + +logger: + enabled: true + # enabled by default + # See apigee-logger in Service accounts and roles used by hybrid + # components. + serviceAccountPath: ${non_prod_service_account_filepath} + # Provide the path relative to the apigee-telemetry chart directory. + # For example: "PROJECT_ID-apigee-non-prod.json" + +metrics: + serviceAccountPath: ${non_prod_service_account_filepath} + # Provide the path relative to the apigee-telemetry chart directory. + # For example: "PROJECT_ID-apigee-non-prod.json" + +udca: + serviceAccountPath: ${non_prod_service_account_filepath} + # Provide the path relative to the apigee-org chart directory. + # For example: "PROJECT_ID-apigee-non-prod.json" + +watcher: + serviceAccountPath: ${non_prod_service_account_filepath} + # Provide the path relative to the apigee-org chart directory. + # For example: "PROJECT_ID-apigee-non-prod.json" diff --git a/tools/apigee-hybrid-terraform/apigee-hybrid-core/setup_apigee.sh b/tools/apigee-hybrid-terraform/apigee-hybrid-core/setup_apigee.sh new file mode 100755 index 000000000..ca687c9a3 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-hybrid-core/setup_apigee.sh @@ -0,0 +1,405 @@ +#!/bin/bash + +# Copyright 2022 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. + +# Default values +APIGEE_VERSION="1.14.2-hotfix.1" +APIGEE_NAMESPACE="apigee" + +# Function to display usage +usage() { + echo "Usage: $0 [options]" + echo "Options:" + echo " -v, --version VERSION Apigee version (default: $APIGEE_VERSION)" + echo " -n, --namespace NAMESPACE Apigee namespace (default: $APIGEE_NAMESPACE)" + echo " -f, --kubeconfig KUBECONFIG Path to kubeconfig file (default: $KUBECONFIG)" + echo " -o, --overrides PATH Path to overrides.yaml file (required)" + echo " -s, --service PATH Path to apigee service template file (required)" + echo " -a, --sa_email SA_EMAIL Path to apigee service accounts template file (required)" + echo " -k, --key PATH Path to service account key JSON file (required)" + echo " -c, --cert PATH Path to environment group certificate file (required)" + echo " -p, --private-key PATH Path to environment group private key file (required)" + echo " -h, --help Display this help message" + exit 1 +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -v|--version) + APIGEE_VERSION="$2" + shift 2 + ;; + -n|--namespace) + APIGEE_NAMESPACE="$2" + shift 2 + ;; + -f|--kubeconfig) + KUBECONFIG_FILE="$2" + shift 2 + ;; + -o|--overrides) + OVERRIDES_YAML_PATH="$2" + shift 2 + ;; + -s|--service) + SERVICE_TEMPLATE_PATH="$2" + shift 2 + ;; + -a|--sa_email) + SA_EMAIL="$2" + shift 2 + ;; + -k|--key) + SA_KEY_JSON_PATH="$2" + shift 2 + ;; + -c|--cert) + ENVGROUP_CERT_PATH="$2" + shift 2 + ;; + -p|--private-key) + ENVGROUP_PRIVATE_KEY_PATH="$2" + shift 2 + ;; + -h|--help) + usage + ;; + *) + echo "Unknown option: $1" + usage + ;; + esac +done + + +# Validate required parameters +if [ -z "$OVERRIDES_YAML_PATH" ] || [ -z "$SERVICE_TEMPLATE_PATH" ] || [ -z "$SA_EMAIL" ] || \ + [ -z "$SA_KEY_JSON_PATH" ] || [ -z "$ENVGROUP_CERT_PATH" ] || \ + [ -z "$ENVGROUP_PRIVATE_KEY_PATH" ]; then + echo "Error: Missing required parameters" + usage +fi + +setup_apigee() { + if [ -z "$APIGEE_NAMESPACE" ]; then + echo "Apigee namespace is required" + exit 1 + fi + + if [ -z "$OVERRIDES_YAML_PATH" ]; then + echo "Apigee overrides YAML is required" + exit 1 + fi + + if [ -z "$SA_KEY_JSON_PATH" ]; then + echo "Apigee SA key JSON is required" + exit 1 + fi + + if [ -z "$ENVGROUP_CERT_PATH" ]; then + echo "Apigee envgroup cert file is required" + exit 1 + fi + + if [ -z "$ENVGROUP_PRIVATE_KEY_PATH" ]; then + echo "Apigee envgroup private key file is required" + exit 1 + fi + + if [ -z "$SERVICE_TEMPLATE_PATH" ]; then + echo "Apigee service template path is required" + exit 1 + fi + + org_name=$(grep -A 1 'org:' "$OVERRIDES_YAML_PATH" | grep 'org:' | awk '{print $2}') + export org_name + + # Set up base directories + export APIGEE_HYBRID_BASE=output/$org_name/apigee-hybrid + export APIGEE_HELM_CHARTS_BASE=helm-charts + + mkdir -p "$APIGEE_HYBRID_BASE/$APIGEE_HELM_CHARTS_BASE" + + # Pull Apigee Helm charts + cd "$APIGEE_HYBRID_BASE/$APIGEE_HELM_CHARTS_BASE" || exit + export APIGEE_HELM_CHARTS_HOME=$PWD + + # Set chart repository and version + export CHART_REPO=oci://us-docker.pkg.dev/apigee-release/apigee-hybrid-helm-charts + export CHART_VERSION=${APIGEE_VERSION} + + # Remove all files in the home directory + rm -rf "${APIGEE_HELM_CHARTS_HOME:?}"/* + + # Pull all required Helm charts + helm pull "$CHART_REPO/apigee-operator" --version "$CHART_VERSION" --untar + helm pull "$CHART_REPO/apigee-datastore" --version "$CHART_VERSION" --untar + helm pull "$CHART_REPO/apigee-env" --version "$CHART_VERSION" --untar + helm pull "$CHART_REPO/apigee-ingress-manager" --version "$CHART_VERSION" --untar + helm pull "$CHART_REPO/apigee-org" --version "$CHART_VERSION" --untar + helm pull "$CHART_REPO/apigee-redis" --version "$CHART_VERSION" --untar + helm pull "$CHART_REPO/apigee-telemetry" --version "$CHART_VERSION" --untar + helm pull "$CHART_REPO/apigee-virtualhost" --version "$CHART_VERSION" --untar + + # Get the filename from the path + apigee_overrides_yaml_filename=$(basename "$OVERRIDES_YAML_PATH") + local apigee_overrides_yaml_filename + apigee_service_template_filename=$(basename "$SERVICE_TEMPLATE_PATH") + local apigee_service_template_filename + apigee_sa_key_json_filename=$(basename "$SA_KEY_JSON_PATH") + local apigee_sa_key_json_filename + apigee_envgroup_cert_file_filename=$(basename "$ENVGROUP_CERT_PATH") + local apigee_envgroup_cert_file_filename + apigee_envgroup_private_key_file_filename=$(basename "$ENVGROUP_PRIVATE_KEY_PATH") + local apigee_envgroup_private_key_file_filename + + echo "apigee_overrides_yaml_filename: $apigee_overrides_yaml_filename" + echo "apigee_sa_key_json_filename: $apigee_sa_key_json_filename" + echo "apigee_envgroup_cert_file_filename: $apigee_envgroup_cert_file_filename" + echo "apigee_envgroup_private_key_file_filename: $apigee_envgroup_private_key_file_filename" + + # Copy the overrides.yaml file + cp "$OVERRIDES_YAML_PATH" "$APIGEE_HELM_CHARTS_HOME/$apigee_overrides_yaml_filename" + cp "$SERVICE_TEMPLATE_PATH" "$APIGEE_HELM_CHARTS_HOME/$apigee_service_template_filename" + + # Copy the sa-key.json file + cp -fr "$SA_KEY_JSON_PATH" "$APIGEE_HELM_CHARTS_HOME/apigee-datastore/$apigee_sa_key_json_filename" + cp -fr "$SA_KEY_JSON_PATH" "$APIGEE_HELM_CHARTS_HOME/apigee-telemetry/$apigee_sa_key_json_filename" + cp -fr "$SA_KEY_JSON_PATH" "$APIGEE_HELM_CHARTS_HOME/apigee-org/$apigee_sa_key_json_filename" + cp -fr "$SA_KEY_JSON_PATH" "$APIGEE_HELM_CHARTS_HOME/apigee-env/$apigee_sa_key_json_filename" + + mkdir -p "$APIGEE_HELM_CHARTS_HOME/apigee-virtualhost/certs/" + + # Copy the cert files + cp -fr "$ENVGROUP_CERT_PATH" "$APIGEE_HELM_CHARTS_HOME/apigee-virtualhost/certs/$apigee_envgroup_cert_file_filename" + cp -fr "$ENVGROUP_PRIVATE_KEY_PATH" "$APIGEE_HELM_CHARTS_HOME/apigee-virtualhost/certs/$apigee_envgroup_private_key_file_filename" +} + +create_namespace() { + local apigee_namespace=$1 + if ! kubectl get namespace "$apigee_namespace" &>/dev/null; then + kubectl create namespace "$apigee_namespace" + fi +} + +enable_control_plane_access() { + local apigee_namespace=$1 + local apigee_overrides_yaml_path=$2 + org_name=$(grep -A 1 'org:' "$apigee_overrides_yaml_path" | grep 'org:' | awk '{print $2}') + local org_name + + TOKEN=$(gcloud auth application-default print-access-token) + export TOKEN + + curl -X PATCH -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type:application/json" \ + "https://apigee.googleapis.com/v1/organizations/$org_name/controlPlaneAccess?update_mask=synchronizer_identities" \ + -d "{\"synchronizer_identities\": [\"serviceAccount:$SA_EMAIL\"]}" + + sleep 5 + + curl -X PATCH -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type:application/json" \ + "https://apigee.googleapis.com/v1/organizations/$org_name/controlPlaneAccess?update_mask=analytics_publisher_identities" \ + -d "{\"analytics_publisher_identities\": [\"serviceAccount:$SA_EMAIL\"]}" + +} + +install_crd() { + kubectl apply -k "$APIGEE_HELM_CHARTS_HOME/apigee-operator/etc/crds/default/" \ + --server-side \ + --force-conflicts \ + --validate=false +} + +install_cert_manager() { + kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.3/cert-manager.yaml + + #Wait for cert-manager to be ready + kubectl wait --for=condition=ready pod -l app=cert-manager -n cert-manager --timeout=120s +} + +install_operator() { + local apigee_namespace=$1 + local apigee_overrides_yaml_path=$2 + + cd "$APIGEE_HELM_CHARTS_HOME" || exit + + helm upgrade operator apigee-operator/ \ + --install \ + --namespace "$apigee_namespace" \ + --atomic \ + -f "$apigee_overrides_yaml_path" + + #Wait for operator to be ready + kubectl wait --for=condition=ready pod -l app=apigee-controller -n "$apigee_namespace" --timeout=120s +} + +install_datastore() { + local apigee_namespace=$1 + local apigee_overrides_yaml_path=$2 + + cd "$APIGEE_HELM_CHARTS_HOME" || exit + + helm upgrade datastore apigee-datastore/ \ + --install \ + --namespace "$apigee_namespace" \ + --atomic \ + -f "$apigee_overrides_yaml_path" +} + +install_telemetry() { + local apigee_namespace=$1 + local apigee_overrides_yaml_path=$2 + + cd "$APIGEE_HELM_CHARTS_HOME" || exit + + helm upgrade telemetry apigee-telemetry/ \ + --install \ + --namespace "$apigee_namespace" \ + --atomic \ + -f "$apigee_overrides_yaml_path" +} + +install_redis() { + local apigee_namespace=$1 + local apigee_overrides_yaml_path=$2 + + cd "$APIGEE_HELM_CHARTS_HOME" || exit + + helm upgrade redis apigee-redis/ \ + --install \ + --namespace "$apigee_namespace" \ + --atomic \ + -f "$apigee_overrides_yaml_path" +} + +install_ingress_manager() { + local apigee_namespace=$1 + local apigee_overrides_yaml_path=$2 + + cd "$APIGEE_HELM_CHARTS_HOME" || exit + + helm upgrade ingress-manager apigee-ingress-manager/ \ + --install \ + --namespace "$apigee_namespace" \ + --atomic \ + -f "$apigee_overrides_yaml_path" +} + +install_org() { + local apigee_namespace=$1 + local apigee_overrides_yaml_path=$2 + org_name=$(grep -A 1 'org:' "$apigee_overrides_yaml_path" | grep 'org:' | awk '{print $2}') + local org_name + + cd "$APIGEE_HELM_CHARTS_HOME" || exit + + helm upgrade "$org_name" apigee-org/ \ + --install \ + --namespace "$apigee_namespace" \ + --atomic \ + -f "$apigee_overrides_yaml_path" +} + +install_env() { + local apigee_namespace=$1 + local apigee_overrides_yaml_path=$2 + + #read the env_name from the overrides.yaml file + apigee_env_name=$(grep -A 1 'envs:' "$apigee_overrides_yaml_path" | grep 'name:' | awk '{print $3}') + local apigee_env_name + + local env_release_name="env-release-$apigee_env_name" + cd "$APIGEE_HELM_CHARTS_HOME" || exit + + helm upgrade "$env_release_name" apigee-env/ \ + --install \ + --namespace "$apigee_namespace" \ + --atomic \ + --set env="$apigee_env_name" \ + -f "$apigee_overrides_yaml_path" + +} + +install_envgroup() { + local apigee_namespace=$1 + local apigee_overrides_yaml_path=$2 + + #read the env_group_name from the overrides.yaml file + apigee_env_group_name=$(grep -A 1 'virtualhosts:' "$apigee_overrides_yaml_path" | grep 'name:' | awk '{print $3}') + local apigee_env_group_name + local env_group_release_name="env-group-release-$apigee_env_group_name" + + helm upgrade "$env_group_release_name" apigee-virtualhost/ \ + --install \ + --namespace "$apigee_namespace" \ + --atomic \ + --set envgroup="$apigee_env_group_name" \ + -f "$apigee_overrides_yaml_path" + +} + +setup_ingress() { + local apigee_namespace=$1 + local apigee_overrides_yaml_path=$2 + + cd "$APIGEE_HELM_CHARTS_HOME" || exit + + #apply the apigee-service.yaml file + kubectl apply -f "$APIGEE_HELM_CHARTS_HOME/apigee-service.yaml" + +} + +setup_kubeconfig() { + + if [ -z "$KUBECONFIG_FILE" ]; then + echo "KUBECONFIG_FILE is not set. Will use default kubeconfig" + else + if [ -f "$KUBECONFIG_FILE" ]; then + export KUBECONFIG=$KUBECONFIG_FILE + else + echo "KUBECONFIG_FILE does not exist" + fi + fi + echo "Checking if kubectl is configured correctly" + if ! kubectl get nodes; then + echo "Failed to get nodes" + exit 1 + fi +} + +# Main function +main() { + setup_apigee + setup_kubeconfig + create_namespace "$APIGEE_NAMESPACE" + enable_control_plane_access "$APIGEE_NAMESPACE" "overrides.yaml" + install_crd + install_cert_manager + install_operator "$APIGEE_NAMESPACE" "overrides.yaml" + install_datastore "$APIGEE_NAMESPACE" "overrides.yaml" + install_telemetry "$APIGEE_NAMESPACE" "overrides.yaml" + install_redis "$APIGEE_NAMESPACE" "overrides.yaml" + install_ingress_manager "$APIGEE_NAMESPACE" "overrides.yaml" + install_org "$APIGEE_NAMESPACE" "overrides.yaml" + install_env "$APIGEE_NAMESPACE" "overrides.yaml" + install_envgroup "$APIGEE_NAMESPACE" "overrides.yaml" + setup_ingress "$APIGEE_NAMESPACE" "overrides.yaml" +} + +# Run main function +main diff --git a/tools/apigee-hybrid-terraform/apigee-hybrid-core/variables.tf b/tools/apigee-hybrid-terraform/apigee-hybrid-core/variables.tf new file mode 100644 index 000000000..7e470273c --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-hybrid-core/variables.tf @@ -0,0 +1,185 @@ +#!/bin/bash + +# Copyright 2022 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. + +####################################### +#### GCP/Apigee Specific variables #### +####################################### + +variable "project_id" { + description = "The GCP project ID where Apigee Hybrid will be set up." + type = string +} + +variable "region" { + description = "The GCP region for Apigee resources like analytics and runtime instance location." + type = string + default = "us-central1" +} + +variable "apigee_org_display_name" { + description = "Display name for the Apigee Organization. If create_org is true, this will be used." + type = string + default = "Apigee Hybrid Organization" # Generic default +} + +variable "apigee_env_name" { + description = "Name for the Apigee Environment (e.g., dev, test, prod)." + type = string + default = "dev" +} + +variable "kubeconfig" { + description = "Path to the Kubernetes configuration file." + type = string + default = "" +} + + +variable "apigee_env_display_name" { + description = "Display name for the Apigee Environment." + type = string + default = "Development Environment" +} + +variable "apigee_instance_name" { + description = "Name for the Apigee Runtime Instance (representing your K8s cluster)." + type = string + default = "hybrid-instance-1" +} + +variable "apigee_envgroup_name" { + description = "Name for the Apigee Environment Group." + type = string + default = "api-proxy-group" +} + +variable "apigee_envgroup_hostnames" { + description = "List of hostnames for the Environment Group." + type = list(string) + # Example: default = ["api.example.com"] # Must be provided by calling module +} + +variable "apigee_cassandra_replica_count" { + description = "Cassandra Replica Count." + type = number + default = 1 # For non-prod; adjust for prod +} + +variable "apigee_install" { + description = "Indicates whether to run the Apigee setup script. Set to true to install Apigee, false otherwise." + type = bool + default = true +} + +variable "create_org" { + description = "Indicates whether to create the Apigee organization. Set to true to create, false if it already exists." + type = bool + default = false # Safer default; often org exists or is managed separately +} + +variable "billing_type" { + description = "Billing type for the Apigee organization (EVALUATION or PAID). Only used if create_org is true." + type = string + default = "EVALUATION" + validation { + condition = contains(["EVALUATION", "PAID", "SUBSCRIPTION"], var.billing_type) + error_message = "Billing type must be EVALUATION, PAID or SUBSCRIPTION." + } +} + +variable "overrides_template_path" { + description = "Path to the overrides template file (e.g., overrides-templates.yaml)." + type = string + default = "" # Will be set to path.module in main.tf if not overridden +} + +variable "apigee_service_account_name" { + description = "The name of the service account" + type = string + default = "apigee-svc-tf" # Will be set to path.module in main.tf if not overridden +} + +variable "service_template_path" { + description = "Path to the Apigee service template file (e.g., apigee-service-template.yaml)." + type = string + default = "" # Will be set to path.module in main.tf if not overridden +} + +variable "apigee_namespace" { + description = "The Kubernetes namespace where Apigee components will be deployed." + type = string + default = "apigee" +} + +variable "ingress_name" { + description = "Name for the ingress gateway (max 17 characters)." + type = string + default = "apigee-ingress" + validation { + condition = length(var.ingress_name) <= 17 + error_message = "Ingress name must be 17 characters or less." + } +} + +variable "ingress_svc_annotations" { + description = "A map of annotations to apply to the ingress gateway service. Example: { \"service.beta.kubernetes.io/azure-load-balancer-internal\": \"true\" }" + type = map(string) + default = {} +} + +variable "apigee_version" { + description = "Version of Apigee Hybrid to install." + type = string + # Example: default = "1.14.2-hotfix.1" # Must be provided by calling module +} + +variable "cluster_name" { + description = "Name of the Kubernetes cluster where Apigee Hybrid will be deployed. Used in overrides.yaml." + type = string + # Example: "aks-apigee-cluster" # Must be provided by calling module +} + +variable "apigee_org_name" { + description = "Name of the Apigee organization. Typically the GCP Project ID. Used in overrides.yaml." + type = string + # If not provided, project_id will be used. + default = "" +} + +variable "tls_apigee_self_signed" { + description = "Whether to use self-signed certificates for Apigee TLS. If true, self-signed certs will be generated. If false, provide tls_apigee_cert and tls_apigee_key." + type = bool + default = true +} + +variable "tls_apigee_cert_path" { + description = "The TLS certificate for Apigee. Required if tls_apigee_self_signed is false." + type = string + default = "" +} + +variable "tls_apigee_key_path" { + description = "The TLS private key for Apigee. Required if tls_apigee_self_signed is false." + type = string + default = "" + sensitive = true +} + +variable "apigee_lb_ip" { + type = string + description = "IP address for the Apigee Load Balancer." + default = "" +} diff --git a/tools/apigee-hybrid-terraform/apigee-on-aks/.gitignore b/tools/apigee-hybrid-terraform/apigee-on-aks/.gitignore new file mode 100644 index 000000000..9d6911563 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-aks/.gitignore @@ -0,0 +1,26 @@ +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* +*.tfplan + +# Crash log files +crash.log + +# Exclude all .tfvars files, which are likely to contain sentitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Ignore CLI configuration files +.terraformrc +terraform.rc diff --git a/tools/apigee-hybrid-terraform/apigee-on-aks/README.md b/tools/apigee-hybrid-terraform/apigee-on-aks/README.md new file mode 100644 index 000000000..2f343f3a2 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-aks/README.md @@ -0,0 +1,280 @@ +## Disclaimer +This tool is open-source software. It is not an officially supported Google product. It is not a part of Apigee, or any other officially supported Google Product. + +## Architecture + +The following diagram illustrates the architecture of Apigee Hybrid on Azure AKS: + +![Apigee Hybrid on Azure AKS Architecture](../diagram/apigee_hybrid_aks.png) + +The architecture consists of: + +1. **Client Layer**: + - Web/Mobile clients making API calls to the AKS cluster + +2. **Azure Infrastructure**: + - Network Load Balancer for distributing incoming traffic + - AKS Cluster with: + - Runtime Pool for Apigee runtime components + - Data Pool for Apigee data components + - NAT Gateway for outbound connectivity + +3. **Google Cloud Platform**: + - Apigee Organization for API management + - Cloud Logging for log collection + - Cloud Monitoring for metrics and observability + + +## How to Setup Apigee hybrid on Azure AKS Clusters using Terraform + +The Terraform configuration defines a new Virtual Network (VNet) in which to provision the Azure Kubernetes Service (AKS) cluster. It uses the `azurerm` provider to create the required Azure resources, including the AKS cluster, node pools (with auto-scaling capabilities), Network Security Groups (NSGs), and necessary Azure RBAC configurations (e.g., using Managed Identities or Service Principals). + +Open the `main.tf` file to review the resource configuration. The `azurerm_kubernetes_cluster` resource configures the cluster, including a default system node pool. Additional user node pools can be defined using `azurerm_kubernetes_cluster_node_pool` resources, for example, to have different VM sizes or capabilities for specific workloads (like Apigee runtime components). + +Once the terraform provisions the aks infrastructure, it proceeds to create Apigee Organization, Environment and Environment Group and installs Apigee Hybrid. + + +## Getting Started + +1. **Setup an Azure Account/Subscription** if you don't have one. You can start with a free account [here](https://azure.microsoft.com/en-us/free/). +2. **Create an Azure Service Principal or use your User Account**: + * For automation and CI/CD, it's recommended to create a Service Principal with the necessary permissions (e.g., "Contributor" on the subscription or a specific resource group). Follow instructions [here](https://learn.microsoft.com/en-us/azure/developer/terraform/authenticate-to-azure?tabs=bash#create-a-service-principal). + * Alternatively, you can authenticate as a user via Azure CLI. + +3. **Download and install Terraform** to your local terminal as described [here](https://developer.hashicorp.com/terraform/install). +4. **Download and install the Azure CLI (az)** to your local terminal from where Terraform would be run, as described [here](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli). +5. **Download and install Helm** (version 3.15+ recommended, check Apigee docs for specific version compatibility). +6. **Install Google Cloud SDK**: + ```bash + # Check if gcloud is installed + gcloud version + + # If not installed, follow instructions at: + # https://cloud.google.com/sdk/docs/install + # Ensure you have the latest version + ``` +7. **Install kubectl**: + ```bash + # Check if kubectl is installed + kubectl version --client + + # If not installed, follow instructions at: + # https://kubernetes.io/docs/tasks/tools/install-kubectl/ + # Ensure version 1.29 or higher + ``` +8. Run `terraform init` to initialize Terraform and download necessary providers. + +## Setup Steps + +1. **Authenticate with Azure**: + * **Interactive Login (User Account)**: Run `az login`. This command will open a browser for authentication. The Azure CLI will then store your credentials locally. + ![Azure Config](images/azure-config.png "Azure Config") + * **Service Principal**: If you created a Service Principal, ensure your environment variables are set for Terraform to authenticate, or configure them in the Azure provider block: + ```bash + export ARM_CLIENT_ID="your-sp-app-id" + export ARM_CLIENT_SECRET="your-sp-password" + export ARM_SUBSCRIPTION_ID="your-subscription-id" + export ARM_TENANT_ID="your-tenant-id" + ``` +2. **Configure Google Cloud Authentication**: + There are two ways to authenticate with Google Cloud: + + a) **User Account Authentication**: + * Ensure you have the Google Cloud SDK (gcloud) installed and configured + * Run `gcloud auth application-default login` to authenticate + * Set your project: `gcloud config set project ` + + b) **Service Account Authentication**: + * Create a service account with appropriate permissions (Owner/Editor) + * Download the service account key JSON file + * Set the environment variable: `export GOOGLE_APPLICATION_CREDENTIALS="path/to/your/service-account-key.json"` + * Run `gcloud auth activate-service-account --key-file="path/to/your/service-account-key.json"` + * Set your project: `gcloud config set project ` + * Alternatively, you can specify the credentials file path in your Terraform provider configuration: + ```hcl + provider "google" { + credentials = file("path/to/your/service-account-key.json") + project = "" + } + ``` + + Note: + * Ensure that Organization Policy is not disabled to create service account and associated Service Account Key + * Ensure that the user or service account performing terraform has the permissions to access Google Cloud resources. While not recommended but roles like `roles/editor` or `roles/owner` should ensure all tasks completes successfully + + +3. **Customize the Terraform configuration files**: + * Review `main.tf` (and any module files) to adjust Azure resource definitions like VNet address spaces, AKS cluster version, node pool configurations (VM sizes, count, taints, labels for Apigee workloads). + * Update `terraform.tfvars` file (or create one, e.g., `terraform.tfvars`) with your specific values (e.g., Azure region and Apigee Organization etc). + +#### **terraform.tfvars Variable Reference** + +**Descriptions for each variable** listed below the table for more detail. + +| Variable | Description | Example/Default | +|----------|-------------|----------------| +| `azure_location` | Azure region for resources | `"eastus"` | +| `gcp_project_id` | GCP Project ID for Apigee | `"apigee-aks-example1"` | +| `gcp_region` | GCP region for Apigee resources | `"us-central1"` | +| `apigee_org_name` | Apigee organization name (usually same as GCP project) | `"apigee-aks-example1"` | +| `apigee_org_display_name` | Display name for Apigee org | `"Apigee on AKS Example"` | +| `apigee_env_name` | Apigee environment name | `"dev"` | +| `apigee_envgroup_name` | Apigee environment group name | `"dev-group"` | +| `apigee_namespace` | Kubernetes namespace for Apigee | `"apigee"` | +| `apigee_version` | Apigee Hybrid version | `"1.14.2-hotfix.1"` | +| `apigee_cassandra_replica_count` | Cassandra replica count | `1` | +| `hostnames` | List of hostnames for Apigee env group | `["api.example.com", "api-dev.example.com"]` | +| `tls_apigee_self_signed` | Use self-signed TLS certs (`true`/`false`) | `true` | +| `tls_apigee_cert_path` | Path to TLS cert (if not self-signed) | `"path/to/your/tls.crt"` | +| `tls_apigee_key_path` | Path to TLS key (if not self-signed) | `"path/to/your/tls.key"` | +| `apigee_lb_ip` | (Optional) Static IP for LB | `"4.156.46.192"` | +| `create_org` | Create Apigee org (`true`/`false`) | `true` | +| `apigee_install` | Install Apigee Hybrid (`true`/`false`) | `true` | +| `ingress_name` | Name for ingress resource | `"apigee-ing"` | +| `ingress_svc_annotations` | Service annotations for LB (cloud-specific) | `{}` | + + +4. **Run `terraform plan`**: + Validate the list of Azure resources to be created. The exact count will vary based on your configuration. Review the plan carefully to ensure it matches your expectations. + +5. **Run `terraform apply`**: + This will provision the Azure resources and create the AKS cluster. Confirm the apply when prompted. This process can take several minutes. + +## What Happens During Terraform Apply + +When you run `terraform apply`, the following resources are created in sequence: + +1. **Azure Infrastructure Setup**: + - Creates a new Resource Group with a random suffix + - Sets up a Virtual Network (VNet) with address space 10.0.0.0/16 + - Creates a subnet for AKS nodes (10.0.1.0/24) + - Provisions a NAT Gateway with a public IP for outbound connectivity + - Associates the NAT Gateway with the AKS subnet + +2. **AKS Cluster Creation**: + - Creates the main AKS cluster with a system node pool + - Configures network plugin and policy as "azure" + - Sets up service CIDR (10.1.0.0/16) and DNS service IP (10.1.0.10) + - Enables system-assigned managed identity + +3. **Additional Node Pools**: + - Creates "apigeerun" node pool for Apigee runtime components + - Creates "apigeedata" node pool for Apigee data components + - Both pools support auto-scaling if enabled + - Configures appropriate VM sizes and disk sizes for each workload + +4. **GCP/Apigee Setup** (if `create_org=true`): + - Enables required Google Cloud APIs (Apigee, IAM, Compute, etc.) + - Creates an Apigee organization in your GCP project + - Sets up an Apigee environment (e.g., "dev") + - Creates an environment group with specified hostnames + - Attaches the environment to the environment group + +5. **Service Account and Certificate Setup**: + - Creates a GCP service account for Apigee Hybrid + - Generates a service account key + - Creates self-signed TLS certificates for the environment group hostnames + - Saves all credentials and certificates to the `output/` directory + +6. **Apigee Hybrid Installation** (if `apigee_install=true`): + - Creates the Apigee namespace in the EKS cluster + - Enables control plane access for the service account + - Installs required Kubernetes components: + - Custom Resource Definitions (CRDs) + - cert-manager + - Apigee operator + - Deploys Apigee components in sequence: + - Datastore (Cassandra) + - Telemetry + - Redis + - Ingress Manager + - Organization + - Environment + - Environment Group + - Sets up the ingress gateway with the specified configuration + + +The entire process typically takes 15-30 minutes to complete, depending on the size of your cluster and the number of resources being created. + + +## Accessing the Cluster + +Terraform generates kubeconfig specific to the output directory and it can be found at output//apiigee-kubeconfig. + +To access the cluster, configure your `kubectl` to use the generated kubeconfig file: +```bash +export KUBECONFIG=output//apigee-kubeconfig +``` + +```bash +kubectl get pods -A +``` + + +## Accessing Apigee Endpoint + +* Get the ingress IP/DNS to access Apigee +```bash +kubectl get pods -n apigee +kubectl get svc dev-group -n apigee -o jsonpath='{.status.loadBalancer.ingress[0].ip}' +``` +* Add the ingress IP/DNS to Apigee Environment Group Hostnames through Apigee UI + +* Access the healthz endpoint +```bash +curl -H 'User-Agent: GoogleHC' https://api.example.com/healthz/ingress -k \ + --resolve "api.example.com:443:your-ingress-ip>" +``` + +## Cleanup + +When you're done with the Apigee hybrid setup and want to remove all created resources, follow these steps: + +1. **Remove Apigee Hybrid Components**: + ```bash + # Delete Apigee hybrid components from the cluster + helm uninstall <> -n apigee + ``` + +2. **Destroy Terraform Resources**: + ```bash + # Remove all Azure resources created by Terraform + terraform destroy + ``` + This will remove: + - The AKS cluster and all node pools + - Virtual Network and subnets + - NAT Gateway and associated resources + - Resource Group + - All other Azure resources created by the Terraform configuration + - Apigee Organization, Environment and Environment Group + +3. **Clean Up Local Files**: + ```bash + # Remove generated certificates and keys + rm -rf output/${PROJECT_ID}/ + + # Remove Terraform state files + rm -f terraform.tfstate* + ``` + +4. **Optional: Remove GCP Resources**: + + Terraform destroy should clean this up but in case of failure, you can + delete the GCP resources individually + + If you created GCP resources (like Apigee organization, environment, etc.), you may want to remove them as well: + ```bash + # Delete Apigee environment group + gcloud apigee envgroups delete ${ENVGROUP_NAME} --organization=${PROJECT_ID} + + # Delete Apigee environment + gcloud apigee environments delete ${ENV_NAME} --organization=${PROJECT_ID} + + # Delete Apigee organization (if created) + gcloud apigee organizations delete ${PROJECT_ID} + ``` + +Note: The `terraform destroy` command will prompt for confirmation before proceeding. Make sure you have backups of any important data before running the cleanup commands. + + diff --git a/tools/apigee-hybrid-terraform/apigee-on-aks/clean_up.sh b/tools/apigee-hybrid-terraform/apigee-on-aks/clean_up.sh new file mode 100755 index 000000000..2370c189f --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-aks/clean_up.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Copyright 2022 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. + +rm -fr .terraform* +rm -fr terraform.tfstate* +rm -fr output diff --git a/tools/apigee-hybrid-terraform/apigee-on-aks/images/apigee_hybrid_aks.png b/tools/apigee-hybrid-terraform/apigee-on-aks/images/apigee_hybrid_aks.png new file mode 100644 index 000000000..615f41e6b Binary files /dev/null and b/tools/apigee-hybrid-terraform/apigee-on-aks/images/apigee_hybrid_aks.png differ diff --git a/tools/apigee-hybrid-terraform/apigee-on-aks/images/azure-config.png b/tools/apigee-hybrid-terraform/apigee-on-aks/images/azure-config.png new file mode 100644 index 000000000..b43cdcd45 Binary files /dev/null and b/tools/apigee-hybrid-terraform/apigee-on-aks/images/azure-config.png differ diff --git a/tools/apigee-hybrid-terraform/apigee-on-aks/main.tf b/tools/apigee-hybrid-terraform/apigee-on-aks/main.tf new file mode 100644 index 000000000..b4eceb56b --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-aks/main.tf @@ -0,0 +1,308 @@ +#!/bin/bash + +# Copyright 2022 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. + + +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.0" # Consider pinning to a specific minor like "~> 3.75" + } + google = { + source = "hashicorp/google" + version = "~> 6.30.0" + } + random = { # Added for random_string + source = "hashicorp/random" + version = "~> 3.5" + } + local = { # Added for local_file kubeconfig + source = "hashicorp/local" + version = "~> 2.4.0" + } + } +} + +provider "azurerm" { + features {} +} + +# Generate random string for resource names +resource "random_string" "suffix" { + length = 6 # Shortened to avoid hitting length limits on some Azure resources + special = false + upper = false + keepers = { + # This key can be anything, e.g., "static_suffix_trigger" + # As long as the value "1" (or any static value) doesn't change, + # the random string will only be generated once and then stored. + # If you ever need to force a new suffix, change this value. + _ = "1" + } +} + +locals { + name_suffix = random_string.suffix.result + cluster_name = "aks-apigee-${local.name_suffix}" + resource_group_name = "rg-apigee-${local.name_suffix}" + output_dir = "${path.module}/output" # Define output directory for kubeconfig etc. +} + +# Ensure the output directory exists for kubeconfig +resource "null_resource" "create_output_dir" { + triggers = { + output_dir_path = local.output_dir + } + provisioner "local-exec" { + command = "mkdir -p ${local.output_dir}" + } +} + +# Create Resource Group +resource "azurerm_resource_group" "rg" { + name = local.resource_group_name # Use the generated name + location = var.azure_location +} + +# Create Virtual Network +resource "azurerm_virtual_network" "vnet" { + name = "vnet-apigee-${local.name_suffix}" + resource_group_name = azurerm_resource_group.rg.name + location = azurerm_resource_group.rg.location + address_space = ["10.0.0.0/16"] +} + +# Create Subnet for AKS +resource "azurerm_subnet" "aks" { + name = "snet-aks-${local.name_suffix}" + resource_group_name = azurerm_resource_group.rg.name + virtual_network_name = azurerm_virtual_network.vnet.name + address_prefixes = ["10.0.1.0/24"] +} + +# Create NAT Gateway (Optional, but good for outbound internet from AKS) +resource "azurerm_public_ip" "nat" { + name = "pip-nat-${local.name_suffix}" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + allocation_method = "Static" + sku = "Standard" +} + +resource "azurerm_nat_gateway" "nat" { + name = "nat-${local.name_suffix}" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + sku_name = "Standard" +} + +resource "azurerm_subnet_nat_gateway_association" "aks" { + subnet_id = azurerm_subnet.aks.id + nat_gateway_id = azurerm_nat_gateway.nat.id +} + +resource "azurerm_nat_gateway_public_ip_association" "aks_nat_gateway_association" { + nat_gateway_id = azurerm_nat_gateway.nat.id + public_ip_address_id = azurerm_public_ip.nat.id +} + +# Create AKS Cluster +resource "azurerm_kubernetes_cluster" "aks" { + name = local.cluster_name + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + dns_prefix = "apigee-${local.name_suffix}" + + # checkov:skip=CKV_AZURE_4: We are using a specific log configuration that is not part of the default audit. + # checkov:skip=CKV_AZURE_5: RBAC is managed externally via Azure Active Directory. + # checkov:skip=CKV_AZURE_117: Disk encryption is handled by Azure's default platform-managed keys for this use case. + # checkov:skip=CKV_AZURE_116: The Azure Policy Add-on is not required for this specific deployment. + # checkov:skip=CKV_AZURE_6: API Server IP ranges are not enabled by design for this specific deployment. + # checkov:skip=CKV_AZURE_115: Private cluster is not required for this testing or development environment. + + + default_node_pool { + name = "system" + node_count = 1 # Min 1 for system + vm_size = "Standard_D4s_v3" # Adjust as needed + vnet_subnet_id = azurerm_subnet.aks.id + enable_auto_scaling = false + } + + network_profile { + network_plugin = "azure" + network_policy = "azure" # Or "calico" + service_cidr = "10.1.0.0/16" + dns_service_ip = "10.1.0.10" + outbound_type = "userAssignedNATGateway" # Using our NAT Gateway + } + + identity { + type = "SystemAssigned" + } + + depends_on = [azurerm_subnet_nat_gateway_association.aks] +} + +# User Node Pool "apigee-runtime" +resource "azurerm_kubernetes_cluster_node_pool" "runtime" { + name = "apigeerun" + kubernetes_cluster_id = azurerm_kubernetes_cluster.aks.id + vm_size = "Standard_D4s_v3" + min_count = var.runtime_pool_enable_autoscaling ? var.runtime_pool_min_count : null + max_count = var.runtime_pool_enable_autoscaling ? var.runtime_pool_max_count : null + enable_auto_scaling = var.runtime_pool_enable_autoscaling + node_count = var.runtime_pool_enable_autoscaling ? null : var.runtime_pool_node_count # Set node_count to null if autoscaling is enabled + + vnet_subnet_id = azurerm_subnet.aks.id + os_disk_size_gb = 128 + os_type = "Linux" + mode = "User" + zones = ["1", "2"] + + node_labels = { + "nodepool-purpose" = "apigee-runtime" + "cloud.google.com/gke-nodepool" = "apigee-runtime" + } + # Add tags to the node pool + tags = { + "nodepool-purpose" = "apigee-runtime" + } +} + +# User Node Pool "apigee-data" +resource "azurerm_kubernetes_cluster_node_pool" "data" { + name = "apigeedata" + kubernetes_cluster_id = azurerm_kubernetes_cluster.aks.id + vm_size = "Standard_D4s_v3" + min_count = var.data_pool_enable_autoscaling ? var.data_pool_min_count : null + max_count = var.data_pool_enable_autoscaling ? var.data_pool_max_count : null + enable_auto_scaling = var.data_pool_enable_autoscaling + node_count = var.data_pool_enable_autoscaling ? null : var.data_pool_node_count + + vnet_subnet_id = azurerm_subnet.aks.id + os_disk_size_gb = 128 + os_type = "Linux" + mode = "User" + zones = ["1"] + + node_labels = { + "nodepool-purpose" = "apigee-data" + "cloud.google.com/gke-nodepool" = "apigee-data" + } + + tags = { + "nodepool-purpose" = "apigee-data" + } +} + +# Data source to retrieve the built-in "Network Contributor" role definition +data "azurerm_role_definition" "network_contributor" { + name = "Network Contributor" +} + +# Assign the "Network Contributor" role to the AKS cluster's SystemAssigned Managed Identity +# on the subnet scope. This grants the AKS identity permission to join the subnet. +resource "azurerm_role_assignment" "aks_subnet_join_permission" { + scope = azurerm_subnet.aks.id + role_definition_id = data.azurerm_role_definition.network_contributor.id + principal_id = azurerm_kubernetes_cluster.aks.identity[0].principal_id # Correctly reference the AKS cluster's SystemAssigned Identity + skip_service_principal_aad_check = true # Essential for Managed Identities +} + + +# Generate kubeconfig for AKS +resource "local_file" "kubeconfig" { + content = azurerm_kubernetes_cluster.aks.kube_config_raw # Use the raw kubeconfig directly + filename = "${path.module}/output/${var.gcp_project_id}/apigee-kubeconfig" + file_permission = "0600" + + depends_on = [ + null_resource.create_output_dir, # Ensure directory exists + azurerm_kubernetes_cluster.aks + ] +} + +resource "null_resource" "cluster_setup" { + triggers = { + timestamp = timestamp() + } + + # Use local-exec provisioner to run a script to configure kubectl + provisioner "local-exec" { + command = "export KUBECONFIG=${abspath("${path.module}/output/${var.gcp_project_id}/apigee-kubeconfig")} && az aks get-credentials --resource-group ${local.resource_group_name} --name ${local.cluster_name} --overwrite-existing" + } + + depends_on = [ + azurerm_kubernetes_cluster_node_pool.runtime, + azurerm_kubernetes_cluster_node_pool.data, + azurerm_kubernetes_cluster.aks, + azurerm_role_assignment.aks_subnet_join_permission # Add dependency here + ] +} + + +# Call the apigee-hybrid-core module +# This module will now handle the setup script execution if var.apigee_install is true +module "apigee_hybrid" { + source = "../apigee-hybrid-core" # Adjust path as needed + + project_id = var.gcp_project_id + region = var.gcp_region # Core module expects 'region' + apigee_org_name = var.apigee_org_name # Passed to core, core decides how to use it (e.g. for overrides or display name) + apigee_org_display_name = var.apigee_org_display_name + apigee_env_name = var.apigee_env_name + apigee_envgroup_name = var.apigee_envgroup_name + apigee_envgroup_hostnames = var.hostnames # Core module expects 'apigee_envgroup_hostnames' + apigee_instance_name = "aks-${local.name_suffix}" # A name for the Apigee instance resource + cluster_name = local.cluster_name # Pass AKS cluster name to core module + kubeconfig = abspath("${local_file.kubeconfig.filename}") # Pass the kubeconfig file path to core module + + apigee_version = var.apigee_version + apigee_namespace = var.apigee_namespace + apigee_cassandra_replica_count = var.apigee_cassandra_replica_count + + + ingress_name = var.ingress_name + ingress_svc_annotations = var.ingress_svc_annotations + + apigee_lb_ip = var.apigee_lb_ip + #TLS related variables + tls_apigee_self_signed = var.tls_apigee_self_signed + tls_apigee_cert_path = var.tls_apigee_cert_path + tls_apigee_key_path = var.tls_apigee_key_path + + create_org = var.create_org # Set to true if you want this module to create the Apigee Org + apigee_install = var.apigee_install # Set to true to run the setup_apigee.sh script from core module + + # Template paths can be omitted if using defaults in core module (${path.module}/) + overrides_template_path = "${path.module}/../apigee-hybrid-core/overrides-templates.yaml" # Example if you want to be explicit + service_template_path = "${path.module}/../apigee-hybrid-core/apigee-service-template.yaml" # Example + + # Pass annotations if needed for Azure internal load balancer for ingress + # ingress_svc_annotations = { + # "service.beta.kubernetes.io/azure-load-balancer-internal": "true" + # # "service.beta.kubernetes.io/azure-load-balancer-internal-subnet": "snet-aks-${local.name_suffix}" # If specific subnet + # } + + depends_on = [ + azurerm_kubernetes_cluster.aks, # Ensure AKS is ready before Apigee core module attempts anything K8s related + azurerm_kubernetes_cluster_node_pool.runtime, + azurerm_kubernetes_cluster_node_pool.data, + local_file.kubeconfig, # Ensure kubeconfig is generated before Apigee tries to use it + null_resource.cluster_setup + ] +} \ No newline at end of file diff --git a/tools/apigee-hybrid-terraform/apigee-on-aks/outputs.tf b/tools/apigee-hybrid-terraform/apigee-on-aks/outputs.tf new file mode 100644 index 000000000..6b02dfd41 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-aks/outputs.tf @@ -0,0 +1,77 @@ +#!/bin/bash + +# Copyright 2022 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. + + +output "apigee_hybrid_non_prod_sa_email" { + description = "Email of the Apigee Non-Prod service account from the core module." + value = module.apigee_hybrid.apigee_non_prod_sa_email +} + +output "apigee_hybrid_non_prod_sa_key_path" { + description = "Path to the saved Apigee Non-Prod service account key file from the core module." + value = module.apigee_hybrid.apigee_non_prod_sa_key_path +} + +output "apigee_hybrid_overrides_yaml_path" { + description = "Path to the generated Apigee Hybrid overrides.yaml file from the core module." + value = module.apigee_hybrid.apigee_overrides_yaml_path +} + +output "apigee_hybrid_service_yaml_path" { + description = "Path to the generated Apigee Hybrid apigee-service.yaml file from the core module." + value = module.apigee_hybrid.apigee_service_yaml_path +} + +output "apigee_hybrid_envgroup_private_key_file_path" { + description = "Path to the self-signed private key file for the Apigee envgroup from the core module." + value = module.apigee_hybrid.apigee_envgroup_private_key_file_path +} + +output "apigee_hybrid_envgroup_cert_file_path" { + description = "Path to the self-signed certificate file for the Apigee envgroup from the core module." + value = module.apigee_hybrid.apigee_envgroup_cert_file_path +} + +output "apigee_hybrid_setup_script_executed_trigger" { + description = "Indicates if the Apigee setup script was triggered from the core module." + value = module.apigee_hybrid.apigee_setup_script_executed_trigger +} + +output "apigee_hybrid_organization_id" { + description = "The ID of the Apigee organization from the core module." + value = module.apigee_hybrid.apigee_organization_id +} + +output "apigee_hybrid_environment_name" { + description = "The name of the Apigee environment from the core module." + value = module.apigee_hybrid.apigee_environment_name +} + +output "apigee_hybrid_envgroup_id" { + description = "The ID of the Apigee environment group from the core module." + value = module.apigee_hybrid.apigee_envgroup_id +} + +# You can also have outputs specific to the apigee-on-aks module, like the kubeconfig path +output "aks_kubeconfig_path" { + description = "Path to the generated kubeconfig file for the AKS cluster." + value = local_file.kubeconfig.filename +} + +output "aks_cluster_name" { + description = "Name of the deployed AKS cluster." + value = azurerm_kubernetes_cluster.aks.name +} \ No newline at end of file diff --git a/tools/apigee-hybrid-terraform/apigee-on-aks/terraform.tfvars b/tools/apigee-hybrid-terraform/apigee-on-aks/terraform.tfvars new file mode 100644 index 000000000..26f361750 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-aks/terraform.tfvars @@ -0,0 +1,70 @@ +#!/bin/bash + +# Copyright 2022 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. + + +# Azure Configuration +azure_location = "eastus" # Using eastus2 for better availability + +# GCP Configuration +gcp_project_id = "apigee-aks-example1" +gcp_region = "us-central1" + +# Apigee Configuration +apigee_org_name = "apigee-aks-example1" # Same as gcp_project_id +apigee_org_display_name = "Apigee on AKS Example" +apigee_env_name = "dev" +apigee_envgroup_name = "dev-group" +apigee_namespace = "apigee" +apigee_version = "1.15.0" +apigee_cassandra_replica_count = 1 + +# Hostnames for Apigee Environment Group +hostnames = [ + "api.example.com", + "api-dev.example.com" +] + +#TLS related variable +tls_apigee_self_signed = true +tls_apigee_cert_path = "path/to/your/tls.crt" +tls_apigee_key_path = "path/to/your/tls.key" + +#Load Balancer IP +#apigee_lb_ip="4.156.46.192" + + +#Installation Options +create_org = true +apigee_install = true + + + +# Ingress Configuration +ingress_name = "apigee-ing" +ingress_svc_annotations = { + # Uncomment and modify these based on your cloud provider + # For AWS: + # "service.beta.kubernetes.io/aws-load-balancer-type" = "nlb" + # "service.beta.kubernetes.io/aws-load-balancer-internal" = "true" + + # For GCP: + # "cloud.google.com/neg" = "{\"ingress\": true}" + # "cloud.google.com/load-balancer-type" = "internal" + + # For Azure: + # "service.beta.kubernetes.io/azure-load-balancer-internal" = "true" + # "service.beta.kubernetes.io/azure-load-balancer-internal-subnet" = "your-subnet-name" +} diff --git a/tools/apigee-hybrid-terraform/apigee-on-aks/terraform.tfvars.sample b/tools/apigee-hybrid-terraform/apigee-on-aks/terraform.tfvars.sample new file mode 100644 index 000000000..453ce0209 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-aks/terraform.tfvars.sample @@ -0,0 +1,60 @@ +#!/bin/bash + +# Copyright 2022 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. + +# Azure Configuration +azure_location = "eastus" + +# GCP Configuration +gcp_project_id = "your-gcp-project-id" +gcp_region = "us-central1" + +# Apigee Configuration + +apigee_org_name = "your-org-name" +apigee_env_name = "dev" +apigee_envgroup_name = "dev-group" +apigee_namespace = "apigee" +apigee_version = "1.15.0" +apigee_org_display_name = "My Apigee Organization" +apigee_env_display_name = "Development Environment" +apigee_instance_name = "apigee-instance" +apigee_cassandra_replica_count = 1 + + +# Hostnames for Apigee Environment Group +hostnames = [ + "api.example.com", + "api-dev.example.com" +] + +#TLS related variable +tls_apigee_self_signed = true +tls_apigee_cert_path = "path/to/your/tls.crt" +tls_apigee_key_path = "path/to/your/tls.key" + +#Load Balancer IP +#apigee_lb_ip="35.188.116.91" + + +create_org=true +apigee_install=true + + +# Optional: Ingress Service Annotations for Azure Internal Load Balancer +# ingress_svc_annotations = { +# "service.beta.kubernetes.io/azure-load-balancer-internal" = "true" +# "service.beta.kubernetes.io/azure-load-balancer-internal-subnet" = "snet-aks-${name_suffix}" +# } diff --git a/tools/apigee-hybrid-terraform/apigee-on-aks/variables.tf b/tools/apigee-hybrid-terraform/apigee-on-aks/variables.tf new file mode 100644 index 000000000..10696955b --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-aks/variables.tf @@ -0,0 +1,200 @@ +#!/bin/bash + +# Copyright 2022 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 "azure_location" { + description = "The Azure region to deploy resources" + type = string + default = "eastus" +} + +variable "gcp_project_id" { + description = "The GCP project ID" + type = string +} + +variable "gcp_region" { + description = "The GCP region for Apigee control plane resources" + type = string + default = "us-central1" +} + +variable "apigee_org_name" { + description = "The name of the Apigee organization (typically the GCP Project ID)." + type = string + # This will be used as the org_name in overrides.yaml if provided, + # otherwise the core module defaults to using var.gcp_project_id. +} + +variable "apigee_env_name" { + description = "The name of the Apigee environment" + type = string + default = "dev" +} + +variable "apigee_envgroup_name" { + description = "The name of the Apigee environment group" + type = string + default = "dev-group" # Changed default to avoid conflict with env_name if both are 'dev' +} + +variable "apigee_namespace" { + description = "The Kubernetes namespace for Apigee" + type = string + default = "apigee" +} + +variable "apigee_version" { + description = "The version of Apigee Hybrid to install" + type = string + # e.g., default = "1.14.2-hotfix.1" - It's better to define this in one place, perhaps here. +} + +variable "hostnames" { + description = "The list of hostnames for the Apigee environment group" + type = list(string) + # e.g., default = ["myapi.example.com"] +} + +# System Node Pool Variables +variable "system_pool_node_count" { + description = "The number of nodes for the system node pool." + type = number + default = 1 +} + + +variable "runtime_pool_enable_autoscaling" { + description = "Whether to enable autoscaling for the Apigee Runtime node pool." + type = bool + default = false +} + +variable "runtime_pool_node_count" { + description = "Node count in case autoscaling is false." + type = number + default = 2 +} + +# Apigee Runtime Node Pool Variables +variable "runtime_pool_min_count" { + description = "Minimum number of nodes for the Apigee Runtime node pool." + type = number + default = 2 +} + +variable "runtime_pool_max_count" { + description = "Maximum number of nodes for the Apigee Runtime node pool." + type = number + default = 2 +} + +variable "data_pool_enable_autoscaling" { + description = "Whether to enable autoscaling for the Apigee Data node pool." + type = bool + default = false +} + +variable "data_pool_node_count" { + description = "Number of nodes for the Apigee Data node pool in case autoscale is false" + type = number + default = 1 +} + +# Apigee Data Node Pool Variables +variable "data_pool_min_count" { + description = "Minimum number of nodes for the Apigee Data node pool." + type = number + default = 1 +} + +variable "data_pool_max_count" { + description = "Maximum number of nodes for the Apigee Data node pool." + type = number + default = 1 +} + +#Add variable for svcAnnotations +variable "ingress_svc_annotations" { + description = "Service Annotations for Apigee Services" + type = map(string) + default = {} +} + +variable "ingress_name" { + description = "The name of the ingress resource for Apigee" + type = string + default = "apigee-ingress" +} + +variable "apigee_org_display_name" { + description = "The display name for the Apigee organization" + type = string + default = "" +} + +variable "apigee_cassandra_replica_count" { + description = "Number of Cassandra replicas for Apigee" + type = number + default = 3 +} + +variable "create_org" { + description = "Whether to create a new Apigee organization" + type = bool + default = true +} + +variable "apigee_install" { + description = "Whether to run the Apigee installation script" + type = bool + default = true +} + +variable "billing_type" { + description = "The billing type for the Apigee organization (EVALUATION or PAID)" + type = string + default = "EVALUATION" + validation { + condition = contains(["EVALUATION", "PAID"], var.billing_type) + error_message = "Billing type must be either EVALUATION or PAID." + } +} + +variable "tls_apigee_self_signed" { + description = "Whether to use self-signed certificates for Apigee TLS. If true, self-signed certs will be generated. If false, provide tls_apigee_cert and tls_apigee_key." + type = bool + default = true +} + +variable "tls_apigee_cert_path" { + description = "The TLS certificate for Apigee. Required if tls_apigee_self_signed is false." + type = string + default = "" +} + +variable "tls_apigee_key_path" { + description = "The TLS private key for Apigee. Required if tls_apigee_self_signed is false." + type = string + default = "" + sensitive = true +} + +variable "apigee_lb_ip" { + type = string + description = "IP address for the Apigee Load Balancer." + default = "" +} \ No newline at end of file diff --git a/tools/apigee-hybrid-terraform/apigee-on-eks/Images/aws-config.png b/tools/apigee-hybrid-terraform/apigee-on-eks/Images/aws-config.png new file mode 100644 index 000000000..b87c8b7a4 Binary files /dev/null and b/tools/apigee-hybrid-terraform/apigee-on-eks/Images/aws-config.png differ diff --git a/tools/apigee-hybrid-terraform/apigee-on-eks/Images/aws-objects.png b/tools/apigee-hybrid-terraform/apigee-on-eks/Images/aws-objects.png new file mode 100644 index 000000000..58968b0f4 Binary files /dev/null and b/tools/apigee-hybrid-terraform/apigee-on-eks/Images/aws-objects.png differ diff --git a/tools/apigee-hybrid-terraform/apigee-on-eks/Images/placeholder.text b/tools/apigee-hybrid-terraform/apigee-on-eks/Images/placeholder.text new file mode 100644 index 000000000..731f0e74e --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-eks/Images/placeholder.text @@ -0,0 +1 @@ +Adding images folder diff --git a/tools/apigee-hybrid-terraform/apigee-on-eks/Images/tf-plan.png b/tools/apigee-hybrid-terraform/apigee-on-eks/Images/tf-plan.png new file mode 100644 index 000000000..b437f9648 Binary files /dev/null and b/tools/apigee-hybrid-terraform/apigee-on-eks/Images/tf-plan.png differ diff --git a/tools/apigee-hybrid-terraform/apigee-on-eks/README.md b/tools/apigee-hybrid-terraform/apigee-on-eks/README.md new file mode 100644 index 000000000..dab13f55a --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-eks/README.md @@ -0,0 +1,227 @@ +## Disclaimer +This tool is open-source software. It is not an officially supported Google product. It is not a part of Apigee, or any other officially supported Google Product. + +## How to Setup Apigee hybrid on AWS EKS Clusters using Terraform + +The terraform configuration defines a new VPC in which to provision the cluster, and uses the public EKS module to create the required resources, including Auto Scaling Groups, security groups, and IAM Roles and Policies. +Open the main.tf file to review the module configuration. The eks_managed_node_groups parameter configures the cluster with three nodes across two node groups. + +### Architecture + +The following diagram illustrates the architecture of Apigee Hybrid on Azure AKS: + +![Apigee Hybrid on EKS Architecture](Images/aws-objects.png) + + + +## Getting Started + +1. **Setup an AWS Account** if you dont have one as described [here](https://aws.amazon.com/free/?gclid=Cj0KCQiA4fi7BhC5ARIsAEV1YibM5aQfcEpKMPjPwUGl-JqNl6fp9-LoTxpHhH2RFh59MFc1_yETcCQaAmHGEALw_wcB&trk=c8882cbf-4c23-4e67-b098-09697e14ffd9&sc_channel=ps&ef_id=Cj0KCQiA4fi7BhC5ARIsAEV1YibM5aQfcEpKMPjPwUGl-JqNl6fp9-LoTxpHhH2RFh59MFc1_yETcCQaAmHGEALw_wcB:G:s&s_kwcid=AL!4422!3!453053794281!e!!g!!create%20aws%20account!10706954804!104359293503&all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all) + +2. **Create an IAM user and a cli user** As described [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html) ( we would use the cli user credentials when configuring our aws cli) + +3. **Download and install Terraform** to your local terminal as described [here](https://developer.hashicorp.com/terraform/install). +4. **Download and install the EKS CLI (aws)**: + ```bash + # Check if aws CLI is installed + aws --version + + # If not installed, follow instructions at: + # https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html + # Ensure you have the latest version + ``` +5. **Download and install Helm** (version 3.15+ recommended, check Apigee docs for specific version compatibility). +6. **Install Google Cloud SDK**: + ```bash + # Check if gcloud is installed + gcloud version + + # If not installed, follow instructions at: + # https://cloud.google.com/sdk/docs/install + # Ensure you have the latest version + ``` +7. **Install kubectl**: + ```bash + # Check if kubectl is installed + kubectl version --client + + # If not installed, follow instructions at: + # https://kubernetes.io/docs/tasks/tools/install-kubectl/ + # Ensure version 1.29 or higher + ``` +8. Run `terraform init` to initialize Terraform and download necessary providers. + + +## Pre-Cluster Setup Steps + +1. Run aws configure - Run aws configure command to configure settings that the AWS Command Line Interface (AWS CLI) uses to interact with AWS. The credentials and config file are created/updated when you run the command aws configure. The credentials file is located at ~/.aws/credentials on Linux or macOS, or at C:\Users\USERNAME\.aws\credentials on Windows. + Output should be similar to below + ![AWS Config](Images/aws-config.png) + +2. **Configure Google Cloud Authentication**: + There are two ways to authenticate with Google Cloud: + + a) **User Account Authentication**: + * Ensure you have the Google Cloud SDK (gcloud) installed and configured + * Run `gcloud auth application-default login` to authenticate + * Set your project: `gcloud config set project ` + + b) **Service Account Authentication**: + * Create a service account with appropriate permissions (Owner/Editor) + * Download the service account key JSON file + * Set the environment variable: `export GOOGLE_APPLICATION_CREDENTIALS="path/to/your/service-account-key.json"` + * Run `gcloud auth activate-service-account --key-file="path/to/your/service-account-key.json"` + * Set your project: `gcloud config set project ` + * Alternatively, you can specify the credentials file path in your Terraform provider configuration: + ```hcl + provider "google" { + credentials = file("path/to/your/service-account-key.json") + project = "" + } + ``` + + Note: + * Ensure that Organization Policy is not disabled to create service account and associated Service Account Key + * Ensure that the user or service account performing terraform has the permissions to access Google Cloud resources. While not recommended but roles like `roles/editor` or `roles/owner` should ensure all tasks completes successfully + + +3. **Customize the Terraform configuration files**: + + Review and update the `terraform.tfvars` file with your specific values. Below is a table of all supported variables, their descriptions, and example/default values: + + | Variable Name | Description | Example/Default Value | + |--------------------------------|-----------------------------------------------------------------------------|--------------------------------------| + | eks_region | AWS region for EKS cluster | "us-west-1" | + | project_id | The GCP project ID | "apigee-eks-example2" | + | region | The GCP region for resources | "us-west1" | + | apigee_org_name | The name of the Apigee organization | "apigee-eks-example2" | + | apigee_env_name | The name of the Apigee environment | "dev" | + | apigee_envgroup_name | The name of the Apigee environment group | "dev-group" | + | cluster_name | Name of the EKS cluster | "apigee-eks" | + | apigee_namespace | Kubernetes namespace for Apigee components | "apigee" | + | apigee_version | Apigee Hybrid version | "1.14.2-hotfix.1" | + | apigee_org_display_name | Display name for the Apigee organization | "My Company Apigee Organization" | + | apigee_env_display_name | Display name for the Apigee environment | "Development Environment" | + | apigee_instance_name | Name of the Apigee instance | "apigee-instance" | + | apigee_cassandra_replica_count | Number of Cassandra replicas (recommended: 3 for production) | 1 | + | hostnames | List of hostnames for the Apigee environment group | ["api.mycompany.com", "api-dev.mycompany.com"] | + | tls_apigee_self_signed | Use self-signed certificates for Apigee TLS (true/false) | true | + | tls_apigee_cert_path | Path to your TLS certificate (if not self-signed) | "path/to/your/tls.crt" | + | tls_apigee_key_path | Path to your TLS private key (if not self-signed) | "path/to/your/tls.key" | + | apigee_lb_ip | IP address for the Apigee Load Balancer (optional, usually auto-assigned) | "" | + | create_org | Whether to create a new Apigee organization (true/false) | true | + | apigee_install | Whether to install Apigee components (true/false) | true | + | ingress_name | Name of the ingress | "apigee-ingress" | + | ingress_svc_annotations | Annotations for the ingress service (map) | { ... } | + | overrides_template_path | Path to the overrides template file (optional) | "../apigee-hybrid-core/overrides-templates.yaml" | + | service_template_path | Path to the service template file (optional) | "../apigee-hybrid-core/apigee-service-template.yaml" | + | billing_type | The billing type for the Apigee organization | "EVALUATION" or "PAID" | + + > **Tip:** You can copy `terraform.tfvars.sample` to `terraform.tfvars` and edit it with your values. + + Example: + ```hcl + project_id = "apigee-eks-example2" + region = "us-west1" + apigee_org_name = "apigee-eks-example2" + # ... + ``` + +4. **Run `terraform plan`**: + Validate the list of Azure resources to be created. The exact count will vary based on your configuration. Review the plan carefully to ensure it matches your expectations. + ![Terraform Plan](Images/tf-plan.png) + +5. **Run `terraform apply`**: + This will provision the AWS resources and create the EKS cluster. Confirm the apply when prompted. This process can take several minutes. + +## What Happens During Apply + +When you run `terraform apply`, the following sequence of events occurs: + +1. **AWS Infrastructure Setup**: + - Creates a new VPC with public and private subnets across two availability zones + - Sets up NAT Gateway for private subnet internet access + - Configures security groups and IAM roles + - Creates an EKS cluster with managed node groups: + - `apigee-runtime` node group with 2 t3.xlarge instances + - `apigee-data` node group with 1 t3.xlarge instance + - Installs the AWS EBS CSI driver for persistent storage + +2. **GCP/Apigee Setup** (if `create_org=true`): + - Enables required Google Cloud APIs (Apigee, IAM, Compute, etc.) + - Creates an Apigee organization in your GCP project + - Sets up an Apigee environment (e.g., "dev") + - Creates an environment group with specified hostnames + - Attaches the environment to the environment group + +3. **Service Account and Certificate Setup**: + - Creates a GCP service account for Apigee Hybrid + - Generates a service account key + - Creates self-signed TLS certificates for the environment group hostnames + - Saves all credentials and certificates to the `output/` directory + +4. **Apigee Hybrid Installation** (if `apigee_install=true`): + - Creates the Apigee namespace in the EKS cluster + - Enables control plane access for the service account + - Installs required Kubernetes components: + - Custom Resource Definitions (CRDs) + - cert-manager + - Apigee operator + - Deploys Apigee components in sequence: + - Datastore (Cassandra) + - Telemetry + - Redis + - Ingress Manager + - Organization + - Environment + - Environment Group + - Sets up the ingress gateway with the specified configuration + +The entire process typically takes 15-30 minutes to complete, depending on your network speed and the size of the cluster. + +## Accessing the Cluster + +Terraform generates kubeconfig specific to the output directory and it can be found at output//apiigee-kubeconfig. + +To access the cluster, configure your `kubectl` to use the generated kubeconfig file: +```bash +export KUBECONFIG=output//apigee-kubeconfig +``` + +```bash +kubectl get pods -A +``` + + + +## Accessing Apigee Endpoint + +* Get the ingress IP/DNS to access Apigee +```bash +kubectl get pods -n apigee +kubectl get svc dev-group -n apigee -o jsonpath='{.status.loadBalancer.ingress[0].ip}' +``` +* Add the ingress IP/DNS to Apigee Environment Group Hostnames through Apigee UI + +* Access the healthz endpoint +```bash +curl -H 'User-Agent: GoogleHC' https://my-eks-alb-123456.us-west-2.elb.amazonaws.com/healthz/ingress -k +``` + +## Multiple clusters + +To create multiple clusters perform the following steps + +Clone the repo to another folder (or copy existing and delete terraform state files and folder) + +Now repeat from steps 3 above + + + +## Clean Up + +To perform a clean up of the aws resources created by terraform + +Step 1. Delete the aws loadbalancers created ( these get created when the ingress is created and also another one when the kubernetes service in part 3 is created). Alternative to this step would be to import the loadbalancers created manually with terraform import so terraform can manage the destruction of these going forward. + +Step 2. Run terraform destroy to delete the aws resources created by terraform diff --git a/tools/apigee-hybrid-terraform/apigee-on-eks/clean_up.sh b/tools/apigee-hybrid-terraform/apigee-on-eks/clean_up.sh new file mode 100755 index 000000000..2370c189f --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-eks/clean_up.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Copyright 2022 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. + +rm -fr .terraform* +rm -fr terraform.tfstate* +rm -fr output diff --git a/tools/apigee-hybrid-terraform/apigee-on-eks/main.tf b/tools/apigee-hybrid-terraform/apigee-on-eks/main.tf new file mode 100644 index 000000000..7cbba4feb --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-eks/main.tf @@ -0,0 +1,257 @@ +#!/bin/bash + +# Copyright 2022 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. + + +provider "aws" { + region = var.eks_region +} + +# Filter out local zones, which are not currently supported +# with managed node groups +data "aws_availability_zones" "available" { + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +locals { + cluster_name = "hybrid-eks-${random_string.suffix.result}" +} + +resource "random_string" "suffix" { + length = 8 + special = false + keepers = { + # This key can be anything, e.g., "static_suffix_trigger" + # As long as the value "1" (or any static value) doesn't change, + # the random string will only be generated once and then stored. + # If you ever need to force a new suffix, change this value. + _ = "1" + } +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "5.8.1" + + name = "hybrid-vpc" + + cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 2) + + private_subnets = ["10.0.1.0/24", "10.0.2.0/24"] + public_subnets = ["10.0.3.0/24", "10.0.4.0/24"] + + enable_nat_gateway = true + single_nat_gateway = true + enable_dns_hostnames = true + + public_subnet_tags = { + "kubernetes.io/role/elb" = 1 + } + + private_subnet_tags = { + "kubernetes.io/role/internal-elb" = 1 + } +} + +module "eks" { + source = "terraform-aws-modules/eks/aws" + version = "20.37.1" + + cluster_name = local.cluster_name + cluster_version = "1.29" + + cluster_endpoint_public_access = true + enable_cluster_creator_admin_permissions = true + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + + eks_managed_node_group_defaults = { + ami_type = "AL2_x86_64" + } + + eks_managed_node_groups = { + one = { + name = "apigee-runtime" + + instance_types = ["t3.xlarge"] + + min_size = 2 + max_size = 2 + desired_size = 2 + tags = { + "cloud.google.com/gke-nodepool" = "apigee-runtime" + } + labels = { + "nodepool-purpose" = "apigee-runtime" + "cloud.google.com/gke-nodepool" = "apigee-runtime" + } + } + + two = { + name = "apigee-data" + + instance_types = ["t3.xlarge"] + + min_size = 1 + max_size = 1 + desired_size = 1 + tags = { + "cloud.google.com/gke-nodepool" = "apigee-data" + } + labels = { + "nodepool-purpose" = "apigee-data" + "cloud.google.com/gke-nodepool" = "apigee-data" + } + } + } + + depends_on = [module.vpc] +} + +# https://aws.amazon.com/blogs/containers/amazon-ebs-csi-driver-is-now-generally-available-in-amazon-eks-add-ons/ +data "aws_iam_policy" "ebs_csi_policy" { + arn = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy" +} + +module "irsa-ebs-csi" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" + version = "5.39.0" + + create_role = true + role_name = "AmazonEKSTFEBSCSIRole-${local.cluster_name}" + provider_url = module.eks.oidc_provider + role_policy_arns = [data.aws_iam_policy.ebs_csi_policy.arn] + oidc_fully_qualified_subjects = ["system:serviceaccount:kube-system:ebs-csi-controller-sa"] + + depends_on = [module.eks] +} + +# Add EBS CSI driver addon after IRSA role is created +resource "aws_eks_addon" "ebs_csi" { + cluster_name = module.eks.cluster_name + addon_name = "aws-ebs-csi-driver" + service_account_role_arn = module.irsa-ebs-csi.iam_role_arn + + depends_on = [module.irsa-ebs-csi] +} + +# Create output directory if it doesn't exist +resource "null_resource" "create_output_dir" { + provisioner "local-exec" { + command = "mkdir -p ${path.module}/output/${var.project_id}" + } +} + +# Generate kubeconfig for EKS +resource "local_file" "kubeconfig" { + content = <<-KUBECONFIG + apiVersion: v1 + kind: Config + current-context: ${module.eks.cluster_name} + contexts: + - context: + cluster: ${module.eks.cluster_name} + user: ${module.eks.cluster_name} + name: ${module.eks.cluster_name} + clusters: + - cluster: + certificate-authority-data: ${base64encode(module.eks.cluster_certificate_authority_data)} + server: ${module.eks.cluster_endpoint} + name: ${module.eks.cluster_name} + users: + - name: ${module.eks.cluster_name} + user: + exec: + apiVersion: client.authentication.k8s.io/v1beta1 + command: aws + args: + - eks + - get-token + - --cluster-name + - ${module.eks.cluster_name} + - --region + - ${var.eks_region} + KUBECONFIG + filename = "${path.module}/output/${var.project_id}/apigee-kubeconfig" + file_permission = "0600" + + depends_on = [ + null_resource.create_output_dir, + module.eks + ] +} + +resource "null_resource" "cluster_setup" { + triggers = { + timestamp = timestamp() + } + + # Use local-exec provisioner to run a script to configure kubectl + provisioner "local-exec" { + command = "export KUBECONFIG=${abspath("${path.module}/output/${var.project_id}/apigee-kubeconfig")} && aws eks update-kubeconfig --name ${local.cluster_name} --region ${var.eks_region}" + } + + depends_on = [module.eks, aws_eks_addon.ebs_csi, local_file.kubeconfig] +} + + +# Add Apigee Hybrid module +module "apigee_hybrid" { + source = "../apigee-hybrid-core" + + project_id = var.project_id + region = var.region + apigee_org_name = var.apigee_org_name + apigee_env_name = var.apigee_env_name + apigee_envgroup_name = var.apigee_envgroup_name + apigee_namespace = var.apigee_namespace + apigee_version = var.apigee_version + cluster_name = local.cluster_name + kubeconfig = abspath("${local_file.kubeconfig.filename}") # Pass the kubeconfig file path to core module + + + apigee_org_display_name = var.apigee_org_display_name + apigee_env_display_name = var.apigee_env_display_name + apigee_instance_name = var.apigee_instance_name + apigee_envgroup_hostnames = var.hostnames + apigee_cassandra_replica_count = var.apigee_cassandra_replica_count + ingress_name = var.ingress_name + ingress_svc_annotations = var.ingress_svc_annotations + overrides_template_path = "${path.module}/../apigee-hybrid-core/overrides-templates.yaml" + service_template_path = "${path.module}/../apigee-hybrid-core/apigee-service-template.yaml" + + apigee_lb_ip = var.apigee_lb_ip + #TLS related variables + tls_apigee_self_signed = var.tls_apigee_self_signed + tls_apigee_cert_path = var.tls_apigee_cert_path + tls_apigee_key_path = var.tls_apigee_key_path + + + apigee_install = var.apigee_install + create_org = var.create_org + billing_type = var.billing_type + + depends_on = [ + module.eks, + aws_eks_addon.ebs_csi, + local_file.kubeconfig, + null_resource.cluster_setup, + ] +} diff --git a/tools/apigee-hybrid-terraform/apigee-on-eks/outputs.tf b/tools/apigee-hybrid-terraform/apigee-on-eks/outputs.tf new file mode 100644 index 000000000..33c14bbe4 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-eks/outputs.tf @@ -0,0 +1,91 @@ +#!/bin/bash + +# Copyright 2022 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. + + +output "cluster_endpoint" { + description = "Endpoint for EKS control plane" + value = module.eks.cluster_endpoint +} + +output "cluster_security_group_id" { + description = "Security group ids attached to the cluster control plane" + value = module.eks.cluster_security_group_id +} + +output "eks_region" { + description = "AWS region" + value = var.eks_region +} + +output "cluster_name" { + description = "Kubernetes Cluster Name" + value = module.eks.cluster_name +} + +output "eks_kubeconfig_path" { + description = "Path to the generated kubeconfig file." + value = local_file.kubeconfig.filename +} + +output "apigee_hybrid_non_prod_sa_email" { + description = "Email of the Apigee Non-Prod service account from the core module." + value = module.apigee_hybrid.apigee_non_prod_sa_email +} + +output "apigee_hybrid_non_prod_sa_key_path" { + description = "Path to the saved Apigee Non-Prod service account key file from the core module." + value = module.apigee_hybrid.apigee_non_prod_sa_key_path +} + +output "apigee_hybrid_overrides_yaml_path" { + description = "Path to the generated Apigee Hybrid overrides.yaml file from the core module." + value = module.apigee_hybrid.apigee_overrides_yaml_path +} + +output "apigee_hybrid_service_yaml_path" { + description = "Path to the generated Apigee Hybrid apigee-service.yaml file from the core module." + value = module.apigee_hybrid.apigee_service_yaml_path +} + +output "apigee_hybrid_envgroup_private_key_file_path" { + description = "Path to the self-signed private key file for the Apigee envgroup from the core module." + value = module.apigee_hybrid.apigee_envgroup_private_key_file_path +} + +output "apigee_hybrid_envgroup_cert_file_path" { + description = "Path to the self-signed certificate file for the Apigee envgroup from the core module." + value = module.apigee_hybrid.apigee_envgroup_cert_file_path +} + +output "apigee_hybrid_setup_script_executed_trigger" { + description = "Indicates if the Apigee setup script was triggered from the core module." + value = module.apigee_hybrid.apigee_setup_script_executed_trigger +} + +output "apigee_hybrid_organization_id" { + description = "The ID of the Apigee organization from the core module." + value = module.apigee_hybrid.apigee_organization_id +} + +output "apigee_hybrid_environment_name" { + description = "The name of the Apigee environment from the core module." + value = module.apigee_hybrid.apigee_environment_name +} + +output "apigee_hybrid_envgroup_id" { + description = "The ID of the Apigee environment group from the core module." + value = module.apigee_hybrid.apigee_envgroup_id +} diff --git a/tools/apigee-hybrid-terraform/apigee-on-eks/terraform.tf b/tools/apigee-hybrid-terraform/apigee-on-eks/terraform.tf new file mode 100644 index 000000000..9d6be54ac --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-eks/terraform.tf @@ -0,0 +1,50 @@ +#!/bin/bash + +# Copyright 2022 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. + + +terraform { + + # cloud { + # workspaces { + # name = "learn-terraform-eks" + # } + # } + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.5.0" + } + + random = { + source = "hashicorp/random" + version = "~> 3.6.1" + } + + tls = { + source = "hashicorp/tls" + version = "~> 4.0.5" + } + + cloudinit = { + source = "hashicorp/cloudinit" + version = "~> 2.3.4" + } + } + + required_version = "~> 1.3" +} + diff --git a/tools/apigee-hybrid-terraform/apigee-on-eks/terraform.tfvars b/tools/apigee-hybrid-terraform/apigee-on-eks/terraform.tfvars new file mode 100644 index 000000000..a612f33bc --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-eks/terraform.tfvars @@ -0,0 +1,79 @@ +#!/bin/bash + +# Copyright 2022 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. + + +# AWS Configuration +eks_region = "us-west-1" # AWS region for EKS cluster + + +# Apigee Configuration +project_id = "apigee-eks-example2" # Replace with your actual GCP project ID +region = "us-west1" #GCP Region +apigee_org_name = "apigee-eks-example2" # Must be unique across all Apigee organizations +apigee_env_name = "dev" # Environment name (dev, test, prod, etc.) +apigee_envgroup_name = "dev-group" # Environment group name +cluster_name = "apigee-eks" # EKS cluster name +apigee_namespace = "apigee" # Kubernetes namespace for Apigee components +apigee_version = "1.15.0" # Apigee Hybrid version +apigee_org_display_name = "My Company Apigee Organization" +apigee_env_display_name = "Development Environment" +apigee_instance_name = "apigee-instance" +apigee_cassandra_replica_count = 1 # Number of Cassandra replicas (recommended: 3 for production) + + +# Hostnames for Apigee Environment Group +# These are the domains that will be used to access your APIs +hostnames = [ + "api.mycompany.com", # Production API endpoint + "api-dev.mycompany.com" # Development API endpoint +] + +#TLS related variable +tls_apigee_self_signed = true +tls_apigee_cert_path = "path/to/your/tls.crt" +tls_apigee_key_path = "path/to/your/tls.key" + +#Load Balancer IP +#apigee_lb_ip="35.188.116.91" + + +create_org = true +apigee_install = true + +# Ingress Configuration +ingress_name = "apigee-ingress" +ingress_svc_annotations = { + # AWS-specific annotations for Network Load Balancer + #"service.beta.kubernetes.io/aws-load-balancer-type" = "nlb" + #"service.beta.kubernetes.io/aws-load-balancer-internal" = "true" + #"service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing" + #"service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled" = "true" + + # Optional: Add these if you need SSL termination + # "service.beta.kubernetes.io/aws-load-balancer-ssl-cert" = "arn:aws:acm:region:account:certificate/certificate-id" + # "service.beta.kubernetes.io/aws-load-balancer-backend-protocol" = "ssl" + # "service.beta.kubernetes.io/aws-load-balancer-ssl-ports" = "443" +} + +# Optional: Paths to template files if you want to use custom templates +# Uncomment and set these if you have custom templates +# overrides_template_path = "../apigee-hybrid-core/overrides-templates.yaml" +# service_template_path = "../apigee-hybrid-core/apigee-service-template.yaml" + +# Billing Configuration +billing_type = "EVALUATION" # Options: "EVALUATION" or "PAID" +# Note: For production use, set this to "PAID" + diff --git a/tools/apigee-hybrid-terraform/apigee-on-eks/terraform.tfvars.sample b/tools/apigee-hybrid-terraform/apigee-on-eks/terraform.tfvars.sample new file mode 100644 index 000000000..339694b40 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-eks/terraform.tfvars.sample @@ -0,0 +1,66 @@ +#!/bin/bash + +# Copyright 2022 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. + + +#EKS Specific Configuration +eks_region = "us-west-1" + +# GCP Configuration +project_id = "your-gcp-project-id" +region = "us-west1" + +# Apigee Configuration +apigee_org_name = "your-org-name" +apigee_env_name = "dev" +apigee_envgroup_name = "dev-group" +apigee_namespace = "apigee" +cluster_name = "apigee" +apigee_version = "1.15.0" +apigee_org_display_name = "My Apigee Organization" +apigee_env_display_name = "Development Environment" +apigee_instance_name = "apigee-instance" +apigee_cassandra_replica_count = 1 + +# Hostnames for Apigee Environment Group +hostnames = [ + "api.example.com", + "api-dev.example.com" +] + +#TLS related variable +tls_apigee_self_signed = true +tls_apigee_cert_path = "path/to/your/tls.crt" +tls_apigee_key_path = "path/to/your/tls.key" + +#Load Balancer IP +#apigee_lb_ip="35.188.116.91" + +create_org=true +apigee_install=true + +# Ingress Configuration +ingress_name = "apigee-ingress" +ingress_svc_annotations = { + # "service.beta.kubernetes.io/aws-load-balancer-type" = "nlb" + # "service.beta.kubernetes.io/aws-load-balancer-internal" = "true" +} + +# Optional: Paths to template files if you want to use custom templates +# overrides_template_path = "path/to/overrides-template.yaml" +# service_template_path = "path/to/service-template.yaml" + +# Billing Configuration +billing_type = "EVALUATION" # or "PAID" diff --git a/tools/apigee-hybrid-terraform/apigee-on-eks/variables.tf b/tools/apigee-hybrid-terraform/apigee-on-eks/variables.tf new file mode 100644 index 000000000..d067737d5 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-eks/variables.tf @@ -0,0 +1,213 @@ +#!/bin/bash + +# Copyright 2022 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 "aws_region" { + description = "AWS region (legacy variable, use eks_region instead)" + type = string + default = "us-west-1" +} + +variable "eks_region" { + description = "AWS region" + type = string + default = "us-west-1" +} + +variable "cluster_name" { + description = "Name of the EKS cluster" + type = string + default = "hybrid-eks" +} + +variable "vpc_cidr" { + description = "CIDR block for the VPC" + type = string + default = "10.0.0.0/16" +} + +variable "subnet_cidrs" { + description = "Map of subnet CIDR blocks" + type = map(list(string)) + default = { + private = ["10.0.1.0/24", "10.0.2.0/24"] + public = ["10.0.3.0/24", "10.0.4.0/24"] + } +} + +variable "node_groups" { + description = "Map of EKS node group configurations" + type = map(object({ + desired_size = number + min_size = number + max_size = number + instance_type = string + disk_size = number + })) + default = { + runtime = { + desired_size = 2 + min_size = 1 + max_size = 4 + instance_type = "t3.xlarge" + disk_size = 100 + } + data = { + desired_size = 2 + min_size = 1 + max_size = 4 + instance_type = "t3.xlarge" + disk_size = 100 + } + } +} + +#Apigee Specific variables + +variable "project_id" { + description = "The GCP project ID" + type = string +} + +variable "region" { + description = "The GCP region for resources" + type = string + default = "us-central1" +} + +variable "apigee_org_name" { + description = "The name of the Apigee organization" + type = string +} + +variable "apigee_env_name" { + description = "The name of the Apigee environment" + type = string + default = "dev" +} + +variable "apigee_envgroup_name" { + description = "The name of the Apigee environment group" + type = string + default = "dev" +} + +variable "apigee_namespace" { + description = "The Kubernetes namespace for Apigee" + type = string + default = "apigee" +} + +variable "apigee_version" { + description = "The version of Apigee to install" + type = string + default = "1.14.2-hotfix.1" +} + +variable "hostnames" { + description = "List of hostnames for the Apigee environment group" + type = list(string) +} + +variable "create_org" { + description = "Whether to create a new Apigee organization" + type = bool + default = true +} + +variable "apigee_org_display_name" { + description = "Display name for the Apigee organization" + type = string + default = "" +} + +variable "apigee_env_display_name" { + description = "Display name for the Apigee environment" + type = string + default = "" +} + +variable "apigee_instance_name" { + description = "Name of the Apigee instance" + type = string + default = "apigee-instance" +} + +variable "apigee_cassandra_replica_count" { + description = "Number of Cassandra replicas" + type = number + default = 3 +} + +variable "ingress_name" { + description = "Name of the ingress" + type = string + default = "apigee-ingress" +} + +variable "ingress_svc_annotations" { + description = "Annotations for the ingress service" + type = map(string) + default = {} +} + +variable "overrides_template_path" { + description = "Path to the overrides template file" + type = string + default = "" +} + +variable "service_template_path" { + description = "Path to the service template file" + type = string + default = "" +} + +variable "apigee_install" { + description = "Whether to install Apigee components" + type = bool + default = true +} + +variable "billing_type" { + description = "The billing type for the Apigee organization" + type = string + default = "EVALUATION" +} +variable "tls_apigee_self_signed" { + description = "Whether to use self-signed certificates for Apigee TLS. If true, self-signed certs will be generated. If false, provide tls_apigee_cert and tls_apigee_key." + type = bool + default = true +} + +variable "tls_apigee_cert_path" { + description = "The TLS certificate for Apigee. Required if tls_apigee_self_signed is false." + type = string + default = "" +} + +variable "tls_apigee_key_path" { + description = "The TLS private key for Apigee. Required if tls_apigee_self_signed is false." + type = string + default = "" + sensitive = true +} + +variable "apigee_lb_ip" { + type = string + description = "IP address for the Apigee Load Balancer." + default = "" +} \ No newline at end of file diff --git a/tools/apigee-hybrid-terraform/apigee-on-gke/README.md b/tools/apigee-hybrid-terraform/apigee-on-gke/README.md new file mode 100644 index 000000000..3678c4fd8 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-gke/README.md @@ -0,0 +1,251 @@ +## Disclaimer +This tool is open-source software. It is not an officially supported Google product. It is not a part of Apigee, or any other officially supported Google Product. + +## Architecture + +The following diagram illustrates the architecture of Apigee Hybrid on Google Kubernetes Engine (GKE): + +![Apigee Hybrid on GKE Architecture](../diagram/apigee_hybrid_gke.png) + +The architecture consists of: + +1. **Client Layer**: + - Web/Mobile clients making API calls to the GKE cluster + +2. **Google Cloud Infrastructure**: + - Cloud Load Balancer for distributing incoming traffic + - GKE Cluster with: + - Runtime Pool for Apigee runtime components + - Data Pool for Apigee data components + - Cloud NAT for outbound connectivity + - VPC Network with appropriate subnets + +3. **Apigee Management Plane**: + - Apigee Organization for API management + - Cloud Logging for log collection + - Cloud Monitoring for metrics and observability + +## How to Setup Apigee Hybrid on GKE Clusters using Terraform + +The Terraform configuration defines a new VPC network in which to provision the Google Kubernetes Engine (GKE) cluster. It uses the `google` provider to create the required GCP resources, including the GKE cluster, node pools (with auto-scaling capabilities), VPC firewall rules, and necessary IAM configurations. + +Open the `main.tf` file to review the resource configuration. The `google_container_cluster` resource configures the cluster, including a default system node pool. Additional user node pools can be defined using `google_container_node_pool` resources, for example, to have different machine types or capabilities for specific workloads (like Apigee runtime components). + +Once the terraform provisions the GKE infrastructure, it proceeds to create Apigee Organization, Environment and Environment Group and installs Apigee Hybrid. + +## Getting Started + +1. **Setup a Google Cloud Project** if you don't have one. You can create one in the [Google Cloud Console](https://console.cloud.google.com/). + +2. **Configure Google Cloud Authentication**: + There are two ways to authenticate with Google Cloud: + + a) **User Account Authentication**: + * Ensure you have the Google Cloud SDK (gcloud) installed and configured + * Run `gcloud auth login` to authenticate the client + * Run `gcloud auth application-default login` for aut + * Set your project: `gcloud config set project ` + + b) **Service Account Authentication**: + * Create a service account with appropriate permissions (Owner/Editor) + * Download the service account key JSON file + * Set the environment variable: `export GOOGLE_APPLICATION_CREDENTIALS="path/to/your/service-account-key.json"` + * Run `gcloud auth activate-service-account --key-file="path/to/your/service-account-key.json"` + * Set your project: `gcloud config set project ` + * Alternatively, you can specify the credentials file path in your Terraform provider configuration: + ```hcl + provider "google" { + credentials = file("path/to/your/service-account-key.json") + project = "" + } + ``` + + Note: + * Ensure that Organization Policy is not disabled to create service account and associated Service Account Key + * Ensure that the user or service account performing terraform has the permissions to access Google Cloud resources. While not recommended but roles like `roles/editor` or `roles/owner` should ensure all tasks completes successfully + +3. **Download and install Helm** (version 3.15+ recommended, check Apigee docs for specific version compatibility). +4. **Install Google Cloud SDK**: + ```bash + # Check if gcloud is installed + gcloud version + + # If not installed, follow instructions at: + # https://cloud.google.com/sdk/docs/install + # Ensure you have the latest version + ``` +5. **Install kubectl**: + ```bash + # Check if kubectl is installed + kubectl version --client + + # If not installed, follow instructions at: + # https://kubernetes.io/docs/tasks/tools/install-kubectl/ + # Ensure version 1.29 or higher + ``` +6. Run `terraform init` to initialize Terraform and download necessary providers. + + +## Pre-Cluster Setup Steps + +1. **Customize the Terraform configuration files**: + Review and update the `terraform.tfvars` file with your specific values. Below is a table of all supported variables, their descriptions, and example/default values: + + | Variable Name | Description | Example/Default Value | + |--------------------------------|-----------------------------------------------------------------------------|--------------------------------------| + | project_id | The GCP project ID | "apigee-eks-example2" | + | region | The GCP region for resources | "us-west1" | + | apigee_org_name | The name of the Apigee organization | "apigee-eks-example2" | + | apigee_env_name | The name of the Apigee environment | "dev" | + | apigee_envgroup_name | The name of the Apigee environment group | "dev-group" | + | cluster_name | Name of the EKS cluster | "apigee-eks" | + | apigee_namespace | Kubernetes namespace for Apigee components | "apigee" | + | apigee_version | Apigee Hybrid version | "1.14.2-hotfix.1" | + | apigee_org_display_name | Display name for the Apigee organization | "My Company Apigee Organization" | + | apigee_env_display_name | Display name for the Apigee environment | "Development Environment" | + | apigee_instance_name | Name of the Apigee instance | "apigee-instance" | + | apigee_cassandra_replica_count | Number of Cassandra replicas (recommended: 3 for production) | 3 | + | hostnames | List of hostnames for the Apigee environment group | ["api.mycompany.com", "api-dev.mycompany.com"] | + | tls_apigee_self_signed | Use self-signed certificates for Apigee TLS (true/false) | true | + | tls_apigee_cert_path | Path to your TLS certificate (if not self-signed) | "path/to/your/tls.crt" | + | tls_apigee_key_path | Path to your TLS private key (if not self-signed) | "path/to/your/tls.key" | + | apigee_lb_ip | IP address for the Apigee Load Balancer (optional, usually auto-assigned) | "" | + | create_org | Whether to create a new Apigee organization (true/false) | true | + | apigee_install | Whether to install Apigee components (true/false) | true | + | ingress_name | Name of the ingress | "apigee-ingress" | + | ingress_svc_annotations | Annotations for the ingress service (map) | { ... } | + | overrides_template_path | Path to the overrides template file (optional) | "../apigee-hybrid-core/overrides-templates.yaml" | + | service_template_path | Path to the service template file (optional) | "../apigee-hybrid-core/apigee-service-template.yaml" | + | billing_type | The billing type for the Apigee organization | "EVALUATION" or "PAID" | + + > **Tip:** You can copy `terraform.tfvars.sample` to `terraform.tfvars` and edit it with your values. + + Example: + ```hcl + project_id = "apigee-eks-example2" + region = "us-west1" + apigee_org_name = "apigee-eks-example2" + # ... + +2. **Run `terraform plan`**: + Validate the list of GCP resources to be created. Review the plan carefully to ensure it matches your expectations. + +3. **Run `terraform apply`**: + This will provision the GCP resources and create the GKE cluster. Confirm the apply when prompted. This process can take several minutes. + +## What Happens During Terraform Apply + +When you run `terraform apply`, the following resources are created in sequence: + +1. **GCP Infrastructure Setup**: + - Creates a new VPC network with appropriate subnets + - Sets up Cloud NAT for outbound connectivity + - Configures firewall rules for the cluster + - Creates necessary IAM service accounts and roles + +2. **GKE Cluster Creation**: + - Creates the main GKE cluster with a system node pool + - Configures network plugin and policy + - Sets up service CIDR and DNS service IP + - Enables workload identity + +3. **Additional Node Pools**: + - Creates "apigeerun" node pool for Apigee runtime components + - Creates "apigeedata" node pool for Apigee data components + - Both pools support auto-scaling if enabled + - Configures appropriate machine types and disk sizes for each workload + +4. **Apigee Setup**: + - Enables required Google Cloud APIs + - Creates a service account for Apigee with necessary IAM roles + - Generates and saves service account key + - Creates self-signed TLS certificates for Apigee environment group + - Generates Apigee overrides.yaml configuration file from the provided template file with mapped variables + - Sets up Apigee organization, environment, and environment group + - Creates a directory output/${PROJECT_ID} to store generated certificates, keys, overrides.yaml and apigee-service.yaml + +5. **Final Configuration**: + - Configures kubectl to connect to the new GKE cluster + - Installs Apigee Hybrid (using Helm) by calling setup_apigee.sh script + - Outputs important information like project ID and kubeconfig + +The entire process typically takes 15-30 minutes to complete, depending on the size of your cluster and the number of resources being created. + +## Accessing the Cluster + +Terraform generates kubeconfig specific to the output directory and it can be found at output//apiigee-kubeconfig. + +To access the cluster, configure your `kubectl` to use the generated kubeconfig file: +```bash +export KUBECONFIG=output//apigee-kubeconfig +``` + +```bash +kubectl get pods -A +``` + + + + +## Accessing Apigee Endpoint + +* Get the ingress IP/DNS to access Apigee +```bash +kubectl get pods -n apigee +kubectl get svc dev-group -n apigee -o jsonpath='{.status.loadBalancer.ingress[0].ip}' +``` +* Add the ingress IP/DNS to Apigee Environment Group Hostnames through Apigee UI + +* Access the healthz endpoint +```bash +curl -H 'User-Agent: GoogleHC' https://api.example.com/healthz/ingress -k \ + --resolve "api.example.com:443:your-ingress-ip>" +``` + +## Cleanup + +When you're done with the Apigee hybrid setup and want to remove all created resources, follow these steps: + +1. **Remove Apigee Hybrid Components**: + ```bash + # Delete Apigee hybrid components from the cluster + helm uninstall <> -n apigee + ``` + +2. **Destroy Terraform Resources**: + ```bash + # Remove all GCP resources created by Terraform + terraform destroy + ``` + This will remove: + - The GKE cluster and all node pools + - VPC network and subnets + - Cloud NAT and associated resources + - All other GCP resources created by the Terraform configuration + +3. **Clean Up Local Files**: + ```bash + # Remove generated certificates and keys + rm -rf output/${PROJECT_ID}/ + + # Remove Terraform state files + rm -f terraform.tfstate* + ``` + +4. **Optional: Remove Apigee Resources**: + + Terraform destroy should clean this up but in case of failure, you can + delete the Apigee resources individually: + + ```bash + # Delete Apigee environment group + gcloud apigee envgroups delete ${ENVGROUP_NAME} --organization=${PROJECT_ID} + + # Delete Apigee environment + gcloud apigee environments delete ${ENV_NAME} --organization=${PROJECT_ID} + + # Delete Apigee organization (if created) + gcloud apigee organizations delete ${PROJECT_ID} + ``` + +Note: The `terraform destroy` command will prompt for confirmation before proceeding. Make sure you have backups of any important data before running the cleanup commands. diff --git a/tools/apigee-hybrid-terraform/apigee-on-gke/clean_up.sh b/tools/apigee-hybrid-terraform/apigee-on-gke/clean_up.sh new file mode 100755 index 000000000..2370c189f --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-gke/clean_up.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Copyright 2022 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. + +rm -fr .terraform* +rm -fr terraform.tfstate* +rm -fr output diff --git a/tools/apigee-hybrid-terraform/apigee-on-gke/main.tf b/tools/apigee-hybrid-terraform/apigee-on-gke/main.tf new file mode 100644 index 000000000..8c5d2e931 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-gke/main.tf @@ -0,0 +1,354 @@ +#!/bin/bash + +# Copyright 2022 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. + +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> 6.30.0" # Ensure compatibility with the Google provider + } + } +} + +provider "google" { + project = var.project_id + region = var.region +} + +# Enable required APIs +resource "google_project_service" "required_apis" { + for_each = toset([ + "compute.googleapis.com", + "container.googleapis.com", + "cloudresourcemanager.googleapis.com", + "apigee.googleapis.com", + "apigeeconnect.googleapis.com", + "cloudkms.googleapis.com", + "servicenetworking.googleapis.com" + ]) + + project = var.project_id + service = each.key + + disable_dependent_services = false + disable_on_destroy = false +} + +# Generate a random suffix for resource names +resource "random_string" "suffix" { + length = 8 + special = false + upper = false + # Add this keepers block + keepers = { + # This key can be anything, e.g., "static_suffix_trigger" + # As long as the value "1" (or any static value) doesn't change, + # the random string will only be generated once and then stored. + # If you ever need to force a new suffix, change this value. + _ = "1" + } +} + +locals { + name_suffix = random_string.suffix.result +} + +# Create VPC Network +resource "google_compute_network" "vpc" { + name = "vpc-apigee-${local.name_suffix}" + auto_create_subnetworks = false + + depends_on = [ + google_project_service.required_apis + ] +} + +# Create Subnet +resource "google_compute_subnetwork" "subnet" { + name = "subnet-apigee-${local.name_suffix}" + ip_cidr_range = "10.0.0.0/16" + region = var.region + network = google_compute_network.vpc.id + + # checkov:skip=CKV_GCP_26: VPC Flow Logs are managed outside of this Terraform module. + + secondary_ip_range { + range_name = "pod-range" + ip_cidr_range = "10.1.0.0/16" + } + + secondary_ip_range { + range_name = "service-range" + ip_cidr_range = "10.2.0.0/16" + } +} + +# Create Cloud Router +resource "google_compute_router" "router" { + name = "router-apigee-${local.name_suffix}" + region = var.region + network = google_compute_network.vpc.id +} + +# Create Cloud NAT +resource "google_compute_router_nat" "nat" { + name = "nat-apigee-${local.name_suffix}" + router = google_compute_router.router.name + region = google_compute_router.router.region + nat_ip_allocate_option = "AUTO_ONLY" + source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES" + + log_config { + enable = true + filter = "ERRORS_ONLY" + } +} + +# Create GKE Cluster +resource "google_container_cluster" "gke" { + name = "gke-apigee-${local.name_suffix}" + location = var.region + deletion_protection = false + + # checkov:skip=CKV_GCP_19: Basic auth is not used; this is a false positive. + # checkov:skip=CKV_GCP_68: Shielded GKE is not enabled by design for this development environment. + # checkov:skip=CKV_GCP_66: Binary Authorization is not required for this development environment. + # checkov:skip=CKV_GCP_65: Google Groups for RBAC is not used in this specific deployment. + # checkov:skip=CKV_GCP_24: The PodSecurityPolicy controller is not used in this specific deployment. + # checkov:skip=CKV_GCP_70: We're not using a Release Channel to allow for specific GKE version control. + # checkov:skip=CKV_GCP_67: Legacy metadata APIs are disabled by default on the latest GKE versions. + # checkov:skip=CKV_GCP_18: We are not exposing the control plane, this is a false positive due to master authorized networks config. + # checkov:skip=CKV_GCP_21: Labels will be applied later via a different process. + # checkov:skip=CKV_GCP_69: The GKE metadata server is not enabled by design. + # checkov:skip=CKV_GCP_61: VPC Flow Logs are managed at the subnet level. + # checkov:skip=CKV_GCP_13: We're using service accounts for authentication instead of client certificates. + # checkov:skip=CKV_GCP_71: Shielded GKE Nodes are not enabled by design for this development environment. + + + # We can't create a cluster with no node pool defined, but we want to only use + # separately managed node pools. So we create the smallest possible default + # node pool and immediately delete it. + remove_default_node_pool = true + initial_node_count = 1 + + network = google_compute_network.vpc.id + subnetwork = google_compute_subnetwork.subnet.id + + ip_allocation_policy { + cluster_secondary_range_name = "pod-range" + services_secondary_range_name = "service-range" + } + + private_cluster_config { + enable_private_nodes = true + enable_private_endpoint = false + master_ipv4_cidr_block = "172.16.0.0/28" + } + + master_authorized_networks_config { + cidr_blocks { + cidr_block = "0.0.0.0/0" # Consider restricting this in production + display_name = "All" + } + } + + # Add network tags for NAT + network_policy { + enabled = true + } + + # Add network tags for NAT + node_config { + tags = ["nat-route"] + } + lifecycle { + ignore_changes = [ + initial_node_count, + ] + } +} + + +# Create Node Pool for Runtime +resource "google_container_node_pool" "runtime" { + name = "apigee-runtime" + location = var.region + cluster = google_container_cluster.gke.name + node_count = 1 + + node_config { + # checkov:skip=CKV_GCP_22: This environment uses a specific disk type, not the default COS. + # checkov:skip=CKV_GCP_68: Secure boot is not enabled by design for this development environment. + # checkov:skip=CKV_GCP_69: The GKE metadata server is not enabled by design. + + machine_type = "e2-standard-4" + disk_size_gb = 100 + disk_type = "pd-standard" + + labels = { + "apigee-runtime" = "true" + "temp-update-trigger" = "true" # Add this line + } + tags = ["apigee-runtime", "nat-route"] + + } + management { + auto_repair = true + auto_upgrade = true + } + lifecycle { + ignore_changes = all + } +} + +# Create Node Pool for Data +resource "google_container_node_pool" "data" { + name = "apigee-data" + location = var.region + cluster = google_container_cluster.gke.name + node_count = 1 + + node_config { + # checkov:skip=CKV_GCP_22: This environment uses a specific disk type, not the default COS. + # checkov:skip=CKV_GCP_68: Secure boot is not enabled by design for this development environment. + # checkov:skip=CKV_GCP_69: The GKE metadata server is not enabled by design. + + machine_type = "e2-standard-4" + disk_size_gb = 100 + disk_type = "pd-standard" + + labels = { + "apigee-data" = "true" + "temp-update-trigger" = "true" # Add this line + } + tags = ["apigee-data", "nat-route"] + + } + management { + auto_repair = true + auto_upgrade = true + } + lifecycle { + ignore_changes = all + } + +} + +# Generate kubeconfig +resource "local_file" "kubeconfig" { + content = <<-KUBECONFIG + apiVersion: v1 + kind: Config + current-context: ${google_container_cluster.gke.name} + contexts: + - context: + cluster: ${google_container_cluster.gke.name} + user: ${google_container_cluster.gke.name} + name: ${google_container_cluster.gke.name} + clusters: + - cluster: + certificate-authority-data: ${google_container_cluster.gke.master_auth[0].cluster_ca_certificate} + server: https://${google_container_cluster.gke.endpoint} + name: ${google_container_cluster.gke.name} + users: + - name: ${google_container_cluster.gke.name} + user: + exec: + apiVersion: client.authentication.k8s.io/v1beta1 + command: gcloud + args: + - container + - clusters + - get-credentials + - ${google_container_cluster.gke.name} + - --region=${var.region} + - --project=${var.project_id} + KUBECONFIG + filename = "${path.module}/output/${var.project_id}/apigee-kubeconfig" + file_permission = "0600" + + depends_on = [ + null_resource.create_output_dir, + google_container_node_pool.runtime, # Ensure node pools are up before module might use kubeconfig + google_container_node_pool.data + ] +} + + +resource "null_resource" "cluster_setup" { + # Use local-exec provisioner to run a script to configure kubectl + triggers = { + timestamp = timestamp() + } + + provisioner "local-exec" { + command = "export KUBECONFIG=${abspath("${path.module}/output/${var.project_id}/apigee-kubeconfig")} && gcloud container clusters get-credentials ${google_container_cluster.gke.name} --region ${var.region} --project ${var.project_id}" + } + depends_on = [ + local_file.kubeconfig, + google_container_cluster.gke, + ] +} + +# Create output directory if it doesn't exist +resource "null_resource" "create_output_dir" { + provisioner "local-exec" { + command = "mkdir -p ${path.module}/output/${var.project_id}" + } +} + +# Use the apigee-hybrid-core module +module "apigee_hybrid" { + source = "../apigee-hybrid-core" + + project_id = var.project_id + region = var.region + apigee_org_name = var.apigee_org_name + apigee_env_name = var.apigee_env_name + apigee_envgroup_name = var.apigee_envgroup_name + apigee_namespace = var.apigee_namespace + apigee_version = var.apigee_version + cluster_name = google_container_cluster.gke.name + kubeconfig = abspath("${path.module}/output/${var.project_id}/apigee-kubeconfig") + + apigee_org_display_name = var.apigee_org_display_name + apigee_env_display_name = var.apigee_env_display_name + apigee_instance_name = var.apigee_instance_name + apigee_envgroup_hostnames = var.hostnames + apigee_cassandra_replica_count = var.apigee_cassandra_replica_count + ingress_name = var.ingress_name + ingress_svc_annotations = var.ingress_svc_annotations + overrides_template_path = "${path.module}/../apigee-hybrid-core/overrides-templates.yaml" # Example if you want to be explicit + service_template_path = "${path.module}/../apigee-hybrid-core/apigee-service-template.yaml" # Example + + apigee_lb_ip = var.apigee_lb_ip + #TLS related variables + tls_apigee_self_signed = var.tls_apigee_self_signed + tls_apigee_cert_path = var.tls_apigee_cert_path + tls_apigee_key_path = var.tls_apigee_key_path + + create_org = var.create_org + apigee_install = var.apigee_install + billing_type = var.billing_type + + depends_on = [ + google_container_cluster.gke, + google_container_node_pool.runtime, + google_container_node_pool.data, + local_file.kubeconfig, + null_resource.cluster_setup, + ] +} diff --git a/tools/apigee-hybrid-terraform/apigee-on-gke/outputs.tf b/tools/apigee-hybrid-terraform/apigee-on-gke/outputs.tf new file mode 100644 index 000000000..7a855f892 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-gke/outputs.tf @@ -0,0 +1,61 @@ +#!/bin/bash + +# Copyright 2022 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. + + +output "gke_cluster_name" { + description = "The name of the GKE cluster." + value = google_container_cluster.gke.name +} + +output "gke_cluster_endpoint" { + description = "The IP address of the GKE cluster endpoint." + value = google_container_cluster.gke.endpoint +} + +output "kubeconfig_path" { + description = "Path to the generated kubeconfig file." + value = local_file.kubeconfig.filename +} + +output "vpc_name" { + description = "The name of the VPC network." + value = google_compute_network.vpc.name +} + +output "subnet_name" { + description = "The name of the subnet." + value = google_compute_subnetwork.subnet.name +} + +output "apigee_organization_id" { + description = "The ID of the Apigee organization." + value = module.apigee_hybrid.apigee_organization_id +} + +output "apigee_environment_name" { + description = "The name of the Apigee environment." + value = module.apigee_hybrid.apigee_environment_name +} + +output "apigee_envgroup_id" { + description = "The ID of the Apigee environment group." + value = module.apigee_hybrid.apigee_envgroup_id +} + +output "apigee_non_prod_sa_email" { + description = "Email of the Apigee Non-Prod service account." + value = module.apigee_hybrid.apigee_non_prod_sa_email +} \ No newline at end of file diff --git a/tools/apigee-hybrid-terraform/apigee-on-gke/terraform.tfvars b/tools/apigee-hybrid-terraform/apigee-on-gke/terraform.tfvars new file mode 100644 index 000000000..27718dda4 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-gke/terraform.tfvars @@ -0,0 +1,65 @@ +#!/bin/bash + +# Copyright 2022 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. + + +# GCP Configuration +project_id = "apigee-gke-example7" +region = "us-central1" + +# Apigee Configuration +apigee_org_name = "apigee-gke-example7" #Same as Projectid +apigee_env_name = "dev" +apigee_envgroup_name = "dev-group" +apigee_namespace = "apigee" +apigee_version = "1.15.0" +apigee_org_display_name = "Apigee GKE Example Organization" +apigee_env_display_name = "Development Environment" +apigee_instance_name = "apigee-instance" +apigee_cassandra_replica_count = 3 + +# Hostnames for Apigee Environment Group +hostnames = [ + "api.example.com", + "api-dev.example.com" +] + + + +#TLS related variable +tls_apigee_self_signed = true +tls_apigee_cert_path = "path/to/your/tls.crt" +tls_apigee_key_path = "path/to/your/tls.key" + +#Load Balancer IP +#apigee_lb_ip="35.188.116.91" + + +create_org = true +apigee_install = true + +# Ingress Configuration +ingress_name = "apigee-ingress" +ingress_svc_annotations = { + # "cloud.google.com/neg" = "{\"ingress\": true}" + # "cloud.google.com/load-balancer-type" = "Internal" +} + +# Optional: Paths to template files if you want to use custom templates +# overrides_template_path = "path/to/overrides-template.yaml" +# service_template_path = "path/to/service-template.yaml" + +# Billing Configuration +billing_type = "EVALUATION" # or "PAID" diff --git a/tools/apigee-hybrid-terraform/apigee-on-gke/terraform.tfvars.sample b/tools/apigee-hybrid-terraform/apigee-on-gke/terraform.tfvars.sample new file mode 100644 index 000000000..6efeefdc7 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-gke/terraform.tfvars.sample @@ -0,0 +1,63 @@ +#!/bin/bash + +# Copyright 2022 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. + + +# GCP Configuration +project_id = "your-gcp-project-id" +region = "us-central1" + +# Apigee Configuration +apigee_org_name = "your-org-name" +apigee_env_name = "dev" +apigee_envgroup_name = "dev-group" +apigee_namespace = "apigee" +apigee_version = "1.15.0" +apigee_org_display_name = "My Apigee Organization" +apigee_env_display_name = "Development Environment" +apigee_instance_name = "apigee-instance" +apigee_cassandra_replica_count = 3 + +# Hostnames for Apigee Environment Group +hostnames = [ + "api.example.com", + "api-dev.example.com" +] + +#TLS related variable +tls_apigee_self_signed = true +tls_apigee_cert_path = "path/to/your/tls.crt" +tls_apigee_key_path = "path/to/your/tls.key" + +#Load Balancer IP +#apigee_lb_ip="35.188.116.91" + +create_org=true +apigee_install=true + + +# Ingress Configuration +ingress_name = "apigee-ingress" +ingress_svc_annotations = { + # "service.beta.kubernetes.io/aws-load-balancer-type" = "nlb" + # "service.beta.kubernetes.io/aws-load-balancer-internal" = "true" +} + +# Optional: Paths to template files if you want to use custom templates +# overrides_template_path = "path/to/overrides-template.yaml" +# service_template_path = "path/to/service-template.yaml" + +# Billing Configuration +billing_type = "EVALUATION" # or "PAID" diff --git a/tools/apigee-hybrid-terraform/apigee-on-gke/variables.tf b/tools/apigee-hybrid-terraform/apigee-on-gke/variables.tf new file mode 100644 index 000000000..711fff563 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-gke/variables.tf @@ -0,0 +1,151 @@ +#!/bin/bash + +# Copyright 2022 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 "project_id" { + description = "The GCP project ID" + type = string +} + +variable "region" { + description = "The GCP region for resources" + type = string + default = "us-central1" +} + +variable "apigee_org_name" { + description = "The name of the Apigee organization" + type = string +} + +variable "apigee_env_name" { + description = "The name of the Apigee environment" + type = string + default = "dev" +} + +variable "apigee_envgroup_name" { + description = "The name of the Apigee environment group" + type = string + default = "dev" +} + +variable "apigee_namespace" { + description = "The Kubernetes namespace for Apigee" + type = string + default = "apigee" +} + +variable "apigee_version" { + description = "The version of Apigee to install" + type = string + default = "1.14.2-hotfix.1" +} + +variable "hostnames" { + description = "List of hostnames for the Apigee environment group" + type = list(string) +} + +variable "create_org" { + description = "Whether to create a new Apigee organization" + type = bool + default = true +} + +variable "apigee_org_display_name" { + description = "Display name for the Apigee organization" + type = string + default = "" +} + +variable "apigee_env_display_name" { + description = "Display name for the Apigee environment" + type = string + default = "" +} + +variable "apigee_instance_name" { + description = "Name of the Apigee instance" + type = string + default = "apigee-instance" +} + +variable "apigee_cassandra_replica_count" { + description = "Number of Cassandra replicas" + type = number + default = 3 +} + +variable "ingress_name" { + description = "Name of the ingress" + type = string + default = "apigee-ingress" +} + +variable "ingress_svc_annotations" { + description = "Annotations for the ingress service" + type = map(string) + default = {} +} + +variable "overrides_template_path" { + description = "Path to the overrides template file" + type = string + default = "" +} + +variable "service_template_path" { + description = "Path to the service template file" + type = string + default = "" +} + +variable "apigee_install" { + description = "Whether to install Apigee components" + type = bool + default = true +} + +variable "billing_type" { + description = "The billing type for the Apigee organization" + type = string + default = "EVALUATION" +} + +variable "tls_apigee_self_signed" { + description = "Whether to use self-signed certificates for Apigee TLS. If true, self-signed certs will be generated. If false, provide tls_apigee_cert and tls_apigee_key." + type = bool + default = true +} + +variable "tls_apigee_cert_path" { + description = "The TLS certificate for Apigee. Required if tls_apigee_self_signed is false." + type = string + default = "" +} + +variable "tls_apigee_key_path" { + description = "The TLS private key for Apigee. Required if tls_apigee_self_signed is false." + type = string + default = "" + sensitive = true +} + +variable "apigee_lb_ip" { + type = string + description = "IP address for the Apigee Load Balancer." + default = "" +} \ No newline at end of file diff --git a/tools/apigee-hybrid-terraform/apigee-on-others/README.md b/tools/apigee-hybrid-terraform/apigee-on-others/README.md new file mode 100644 index 000000000..ae458b57c --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-others/README.md @@ -0,0 +1,195 @@ +# Apigee Hybrid on Existing Kubernetes Clusters + +This directory contains Terraform configurations for deploying Apigee Hybrid on an existing Kubernetes cluster. This setup assumes that you already have a Kubernetes cluster running and properly configured with the necessary node pools for Apigee Hybrid. + +## Prerequisites + +1. **Existing Kubernetes Cluster**: + - A running Kubernetes cluster (version 1.29 or later) + - At least two node pools: + - Runtime node pool for Apigee runtime components with node labels set with key cloud.google.com/gke-nodepool and value apigee-runtime + - Data node pool for Apigee data with node labels set with key cloud.google.com/gke-nodepool and value apigee-data + - Proper network configuration for the cluster + +2. **Kubernetes Access**: + - `kubectl` configured to access your cluster + - `KUBECONFIG` environment variable set or config file in `~/.kube/config` + - Proper RBAC permissions in the cluster + +3. **Google Cloud Setup**: + - Google Cloud SDK installed and configured + - Optional: Project with Apigee API enabled. The script enables it. + - Optional: Service account with necessary permissions. The script automatically creates it. + - Organization Policy allowing service account key creation + +4. **Configure Google Cloud Authentication**: + There are two ways to authenticate with Google Cloud: + + a) **User Account Authentication**: + * Ensure you have the Google Cloud SDK (gcloud) installed and configured + * Run `gcloud auth application-default login` to authenticate + * Set your project: `gcloud config set project ` + + b) **Service Account Authentication**: + * Create a service account with appropriate permissions (Owner/Editor) + * Download the service account key JSON file + * Set the environment variable: `export GOOGLE_APPLICATION_CREDENTIALS="path/to/your/service-account-key.json"` + * Run `gcloud auth activate-service-account --key-file="path/to/your/service-account-key.json"` + * Set your project: `gcloud config set project ` + * Alternatively, you can specify the credentials file path in your Terraform provider configuration: + ```hcl + provider "google" { + credentials = file("path/to/your/service-account-key.json") + project = "" + } + ``` + + Note: + * Ensure that Organization Policy is not disabled to create service account and associated Service Account Key + * Ensure that the user or service account performing terraform has the permissions to access Google Cloud resources. While not recommended but roles like `roles/editor` or `roles/owner` should ensure all tasks completes successfully + +5. **Required Tools**: + - Terraform >= 1.0.0 + - Helm >= 3.10.0 + - kubectl + - gcloud CLI + +## Configuration + +1. **Set up your variables**: + Update `terraform.tfvars` file with your specific values: + + ```hcl + project_id = "apigee-gke-example3" + region = "us-central1" # Default region, change if needed + apigee_org_name = "apigee-gke-example3" + apigee_env_name = "dev" + apigee_envgroup_name = "dev-group" + cluster_name = "apigee" + apigee_namespace = "apigee" + apigee_version = "1.14.2-hotfix.1" + apigee_org_display_name = "My Company Apigee Organization" + apigee_env_display_name = "Development Environment" + apigee_instance_name = "apigee-instance" + apigee_cassandra_replica_count = 1 + + hostnames = [ + "api.mycompany.com", # Production API endpoint + "api-dev.mycompany.com" # Development API endpoint + ] + ingress_name = "apigee-ingress" + + ``` + +2. **Verify Kubernetes Access**: + ```bash + kubectl get nodes + ``` + +3. **Verify Node Pools**: + ```bash + kubectl get nodes --show-labels + ``` + Ensure you have nodes with the appropriate labels for Apigee runtime and data components. + +## Deployment + +1. **Initialize Terraform**: + ```bash + terraform init + ``` + +2. **Review the Plan**: + ```bash + terraform plan + ``` + +3. **Apply the Configuration**: + ```bash + terraform apply + ``` + +## What Gets Deployed + +1. **Apigee Organization**: + - Creates or uses existing Apigee organization + - Sets up environment and environment group + - Configures runtime settings + +2. **Kubernetes Resources**: + - Creates necessary namespaces + - Deploys Apigee runtime components + - Configures service accounts and RBAC + - Sets up ingress and load balancing + +3. **SSL/TLS Configuration**: + - Generates or uses provided SSL certificates + - Configures TLS for the runtime + +## Verification + +1. **Check Apigee Components**: + ```bash + kubectl get pods -n apigee + ``` + +2. **Verify Environment Group**: + ```bash + gcloud apigee envgroups list --organization=$PROJECT_ID + ``` + +3. **Verify Apigee Endpoint**: + +* Get the ingress IP/DNS to access Apigee +```bash +kubectl get pods -n apigee +kubectl get svc dev-group -n apigee -o jsonpath='{.status.loadBalancer.ingress[0].ip}' +``` +* Add the ingress IP/DNS to Apigee Environment Group Hostnames through Apigee UI + +* Access the healthz endpoint +```bash +curl -H 'User-Agent: GoogleHC' https://api.example.com/healthz/ingress -k \ + --resolve "api.example.com:443:your-ingress-ip>" +``` + +## Cleanup + +1. **Remove Apigee Components**: + ```bash + helm uninstall apigee-hybrid -n apigee + ``` + +2. **Destroy Terraform Resources**: + ```bash + terraform destroy + ``` + +3. **Clean Up Local Files**: + ```bash + rm -rf output/${PROJECT_ID}/ + ``` + +## Troubleshooting + +1. **Kubernetes Connection Issues**: + - Verify `kubectl` configuration + - Check cluster accessibility + - Ensure proper RBAC permissions + +2. **Apigee Deployment Issues**: + - Check pod status and logs + - Verify node pool labels + - Review Apigee runtime logs + +3. **SSL/TLS Issues**: + - Verify certificate validity + - Check ingress configuration + - Review TLS settings + +## Additional Resources + +- [Apigee Hybrid Documentation](https://cloud.google.com/apigee/docs/hybrid) +- [Kubernetes Documentation](https://kubernetes.io/docs/) +- [Terraform Documentation](https://www.terraform.io/docs) +- [Helm Documentation](https://helm.sh/docs/) \ No newline at end of file diff --git a/tools/apigee-hybrid-terraform/apigee-on-others/clean_up.sh b/tools/apigee-hybrid-terraform/apigee-on-others/clean_up.sh new file mode 100755 index 000000000..2370c189f --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-others/clean_up.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Copyright 2022 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. + +rm -fr .terraform* +rm -fr terraform.tfstate* +rm -fr output diff --git a/tools/apigee-hybrid-terraform/apigee-on-others/main.tf b/tools/apigee-hybrid-terraform/apigee-on-others/main.tf new file mode 100644 index 000000000..b54234473 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-others/main.tf @@ -0,0 +1,84 @@ +#!/bin/bash + +# Copyright 2022 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. + + +terraform { + required_version = ">= 1.0.0" + required_providers { + google = { + source = "hashicorp/google" + version = "~> 6.30.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.0.0" + } + helm = { + source = "hashicorp/helm" + version = ">= 2.0.0" + } + } +} + +provider "google" { + project = var.project_id + region = var.region +} + +provider "kubernetes" { + # Configuration will be automatically loaded from KUBECONFIG + # or from the default location (~/.kube/config) +} + +provider "helm" { + # Configuration will be automatically loaded from KUBECONFIG + # or from the default location (~/.kube/config) +} + +# Use the apigee-hybrid-core module +module "apigee_hybrid" { + source = "../apigee-hybrid-core" + + project_id = var.project_id + region = var.region + apigee_org_name = var.apigee_org_name + apigee_env_name = var.apigee_env_name + apigee_envgroup_name = var.apigee_envgroup_name + apigee_namespace = var.apigee_namespace + apigee_version = var.apigee_version + cluster_name = var.cluster_name + apigee_org_display_name = var.apigee_org_display_name + apigee_env_display_name = var.apigee_env_display_name + apigee_instance_name = var.apigee_instance_name + apigee_envgroup_hostnames = var.hostnames + apigee_cassandra_replica_count = var.apigee_cassandra_replica_count + ingress_name = var.ingress_name + ingress_svc_annotations = var.ingress_svc_annotations + overrides_template_path = "${path.module}/../apigee-hybrid-core/overrides-templates.yaml" # Example if you want to be explicit + service_template_path = "${path.module}/../apigee-hybrid-core/apigee-service-template.yaml" # Example + + apigee_lb_ip = var.apigee_lb_ip + #TLS related variables + tls_apigee_self_signed = var.tls_apigee_self_signed + tls_apigee_cert_path = var.tls_apigee_cert_path + tls_apigee_key_path = var.tls_apigee_key_path + + create_org = var.create_org + apigee_install = var.apigee_install + billing_type = var.billing_type + + +} \ No newline at end of file diff --git a/tools/apigee-hybrid-terraform/apigee-on-others/outputs.tf b/tools/apigee-hybrid-terraform/apigee-on-others/outputs.tf new file mode 100644 index 000000000..a4da64ec1 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-others/outputs.tf @@ -0,0 +1,35 @@ +#!/bin/bash + +# Copyright 2022 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. + +output "apigee_organization_id" { + description = "The ID of the Apigee organization." + value = module.apigee_hybrid.apigee_organization_id +} + +output "apigee_environment_name" { + description = "The name of the Apigee environment." + value = module.apigee_hybrid.apigee_environment_name +} + +output "apigee_envgroup_id" { + description = "The ID of the Apigee environment group." + value = module.apigee_hybrid.apigee_envgroup_id +} + +output "apigee_non_prod_sa_email" { + description = "Email of the Apigee Non-Prod service account." + value = module.apigee_hybrid.apigee_non_prod_sa_email +} \ No newline at end of file diff --git a/tools/apigee-hybrid-terraform/apigee-on-others/terraform.tfvars b/tools/apigee-hybrid-terraform/apigee-on-others/terraform.tfvars new file mode 100644 index 000000000..7d8479d54 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-others/terraform.tfvars @@ -0,0 +1,75 @@ +#!/bin/bash + +# Copyright 2022 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. + + +# GCP Configuration +project_id = "apigee-gke-example3" # Replace with your actual GCP project ID +region = "us-central1" # Default region, change if needed + +# Apigee Configuration +apigee_org_name = "apigee-gke-example3" # Must be unique across all Apigee organizations +apigee_env_name = "dev" # Environment name (dev, test, prod, etc.) +apigee_envgroup_name = "dev-group" # Environment group name +cluster_name = "apigee" # Kubernetes cluster name +apigee_namespace = "apigee" # Kubernetes namespace for Apigee components +apigee_version = "1.15.0" # Apigee Hybrid version +apigee_org_display_name = "My Company Apigee Organization" +apigee_env_display_name = "Development Environment" +apigee_instance_name = "apigee-instance" +apigee_cassandra_replica_count = 3 # Number of Cassandra replicas (recommended: 3 for production) + +# Hostnames for Apigee Environment Group +# These are the domains that will be used to access your APIs +hostnames = [ + "api.mycompany.com", # Production API endpoint + "api-dev.mycompany.com" # Development API endpoint +] + +#TLS related variable +tls_apigee_self_signed = true +#tls_apigee_cert_path = "tls.crt" +#tls_apigee_key_path = "tls.key" + +#Load Balancer IP +#apigee_lb_ip="35.188.116.91" + +create_org = false +apigee_install = true + +# Ingress Configuration +ingress_name = "apigee-ingress" +ingress_svc_annotations = { + # Uncomment and modify these based on your cloud provider + # For AWS: + # "service.beta.kubernetes.io/aws-load-balancer-type" = "nlb" + # "service.beta.kubernetes.io/aws-load-balancer-internal" = "true" + + # For GCP: + #"networking.gke.io/load-balancer-type": "Internal" + + # For Azure: + # "service.beta.kubernetes.io/azure-load-balancer-internal" = "true" + # "service.beta.kubernetes.io/azure-load-balancer-internal-subnet" = "your-subnet-name" +} + +# Optional: Paths to template files if you want to use custom templates +# Uncomment and set these if you have custom templates +# overrides_template_path = "../apigee-hybrid-core/overrides-templates.yaml" +# service_template_path = "../apigee-hybrid-core/apigee-service-template.yaml" + +# Billing Configuration +billing_type = "EVALUATION" # Options: "EVALUATION" or "PAID" +# Note: For production use, set this to "PAID" diff --git a/tools/apigee-hybrid-terraform/apigee-on-others/terraform.tfvars.sample b/tools/apigee-hybrid-terraform/apigee-on-others/terraform.tfvars.sample new file mode 100644 index 000000000..5884a3253 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-others/terraform.tfvars.sample @@ -0,0 +1,72 @@ +#!/bin/bash + +# Copyright 2022 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. + +# GCP Configuration +project_id = "your-gcp-project-id" +region = "us-central1" + +# Apigee Configuration +apigee_org_name = "your-org-name" +apigee_env_name = "dev" +apigee_envgroup_name = "dev" +apigee_namespace = "apigee" +cluster_name = "apigee" +apigee_version = "1.15.0" +apigee_org_display_name = "My Apigee Organization" +apigee_env_display_name = "Development Environment" +apigee_instance_name = "apigee-instance" +apigee_cassandra_replica_count = 3 + +# Hostnames for Apigee Environment Group +hostnames = [ + "api.example.com", + "api-dev.example.com" +] + +#TLS related variable +tls_apigee_self_signed = true +tls_apigee_cert_path = "path/to/your/tls.crt" +tls_apigee_key_path = "path/to/your/tls.key" + +#Load Balancer IP +#apigee_lb_ip="35.188.116.91" + +create_org=true +apigee_install=true + +# Ingress Configuration +ingress_name = "apigee-ingress" +ingress_svc_annotations = { + # Uncomment and modify these based on your cloud provider + # For AWS: + # "service.beta.kubernetes.io/aws-load-balancer-type" = "nlb" + # "service.beta.kubernetes.io/aws-load-balancer-internal" = "true" + + # For GCP: + # "cloud.google.com/neg" = "{\"ingress\": true}" + # "cloud.google.com/load-balancer-type" = "internal" + + # For Azure: + # "service.beta.kubernetes.io/azure-load-balancer-internal" = "true" + # "service.beta.kubernetes.io/azure-load-balancer-internal-subnet" = "your-subnet-name" +} + +# Optional: Paths to template files if you want to use custom templates +# overrides_template_path = "path/to/overrides-template.yaml" +# service_template_path = "path/to/service-template.yaml" + +# Billing Configuration +billing_type = "EVALUATION" # or "PAID" \ No newline at end of file diff --git a/tools/apigee-hybrid-terraform/apigee-on-others/variables.tf b/tools/apigee-hybrid-terraform/apigee-on-others/variables.tf new file mode 100644 index 000000000..841ac6d36 --- /dev/null +++ b/tools/apigee-hybrid-terraform/apigee-on-others/variables.tf @@ -0,0 +1,157 @@ +#!/bin/bash + +# Copyright 2022 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 "project_id" { + description = "The GCP project ID" + type = string +} + +variable "region" { + description = "The GCP region for resources" + type = string + default = "us-central1" +} + +variable "cluster_name" { + description = "The name of Cluster" + type = string + default = "apigee" +} + +variable "apigee_org_name" { + description = "The name of the Apigee organization" + type = string +} + +variable "apigee_env_name" { + description = "The name of the Apigee environment" + type = string + default = "dev" +} + +variable "apigee_envgroup_name" { + description = "The name of the Apigee environment group" + type = string + default = "dev" +} + +variable "apigee_namespace" { + description = "The Kubernetes namespace for Apigee" + type = string + default = "apigee" +} + +variable "apigee_version" { + description = "The version of Apigee to install" + type = string + default = "1.14.2-hotfix.1" +} + +variable "hostnames" { + description = "List of hostnames for the Apigee environment group" + type = list(string) +} + +variable "create_org" { + description = "Whether to create a new Apigee organization" + type = bool + default = false +} + +variable "apigee_org_display_name" { + description = "Display name for the Apigee organization" + type = string + default = "" +} + +variable "apigee_env_display_name" { + description = "Display name for the Apigee environment" + type = string + default = "" +} + +variable "apigee_instance_name" { + description = "Name of the Apigee instance" + type = string + default = "apigee-instance" +} + +variable "apigee_cassandra_replica_count" { + description = "Number of Cassandra replicas" + type = number + default = 3 +} + +variable "ingress_name" { + description = "Name of the ingress" + type = string + default = "apigee-ingress" +} + +variable "ingress_svc_annotations" { + description = "Annotations for the ingress service" + type = map(string) + default = {} +} + +variable "overrides_template_path" { + description = "Path to the overrides template file" + type = string + default = "" +} + +variable "service_template_path" { + description = "Path to the service template file" + type = string + default = "" +} + +variable "apigee_install" { + description = "Whether to install Apigee components" + type = bool + default = false +} + +variable "billing_type" { + description = "The billing type for the Apigee organization" + type = string + default = "EVALUATION" +} +variable "tls_apigee_self_signed" { + description = "Whether to use self-signed certificates for Apigee TLS. If true, self-signed certs will be generated. If false, provide tls_apigee_cert and tls_apigee_key." + type = bool + default = true +} + +variable "tls_apigee_cert_path" { + description = "The TLS certificate for Apigee. Required if tls_apigee_self_signed is false." + type = string + default = "" +} + +variable "tls_apigee_key_path" { + description = "The TLS private key for Apigee. Required if tls_apigee_self_signed is false." + type = string + default = "" + sensitive = true +} + +variable "apigee_lb_ip" { + type = string + description = "IP address for the Apigee Load Balancer." + default = "" +} diff --git a/tools/apigee-hybrid-terraform/apply_org_policies.sh b/tools/apigee-hybrid-terraform/apply_org_policies.sh new file mode 100755 index 000000000..79e374f8a --- /dev/null +++ b/tools/apigee-hybrid-terraform/apply_org_policies.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# Copyright 2022 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. + +read -r -p "Please enter your Google Cloud project ID: " project +export project + +if [ -z "$project" ]; then + echo "Error: The 'project' variable is not set. Please set it before proceeding." + exit 1 +fi + +apply_constraints() { + echo "Applying organization policies to ${project}..." + gcloud beta resource-manager org-policies disable-enforce compute.requireShieldedVm --project="${project}" + gcloud beta resource-manager org-policies disable-enforce compute.requireOsLogin --project="${project}" + gcloud beta resource-manager org-policies disable-enforce iam.disableServiceAccountCreation --project="${project}" + gcloud beta resource-manager org-policies disable-enforce iam.disableServiceAccountKeyCreation --project="${project}" + gcloud beta resource-manager org-policies disable-enforce compute.skipDefaultNetworkCreation --project="${project}" + + declare -a policies=("constraints/compute.trustedImageProjects" + "constraints/compute.vmExternalIpAccess" + "constraints/compute.restrictSharedVpcSubnetworks" + "constraints/compute.restrictSharedVpcHostProjects" + "constraints/compute.restrictVpcPeering" + "constraints/compute.vmCanIpForward" + ) + + for policy in "${policies[@]}"; do + cat <new_policy.yaml +constraint: $policy +listPolicy: + allValues: ALLOW +EOF + gcloud resource-manager org-policies set-policy new_policy.yaml --project="${project}" + done + echo "Allow upto 30 seconds to Propagate the policy changes" + sleep 30 + echo "Policy Changes done" +} + +apply_constraints diff --git a/tools/apigee-hybrid-terraform/diagram/aks_draw.py b/tools/apigee-hybrid-terraform/diagram/aks_draw.py new file mode 100644 index 000000000..11236fd19 --- /dev/null +++ b/tools/apigee-hybrid-terraform/diagram/aks_draw.py @@ -0,0 +1,71 @@ +#!/bin/bash + +# Copyright 2022 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. + + +from diagrams import Diagram, Cluster, Edge +from diagrams.gcp.api import Apigee +from diagrams.azure.compute import KubernetesServices, VM +from diagrams.azure.network import LoadBalancers +from diagrams.onprem.client import Users +from diagrams.onprem.network import Internet +from diagrams.gcp.operations import Logging, Monitoring + +with Diagram("Apigee Hybrid on Azure AKS", + show=False, + direction="LR", + outformat="png", + filename="apigee_hybrid_aks", + graph_attr={ + "splines": "ortho", + "nodesep": "0.8", + "ranksep": "0.8", + "pad": "0.5", + "fontsize": "45", + "fontname": "Arial", + "fontcolor": "#2D3436", + "bgcolor": "white" + }): + # Client + client = Users("Web/Mobile\nClients") + + # Azure Resources + with Cluster("Azure", graph_attr={"fontsize": "20"}): + with Cluster("AKS Cluster", graph_attr={"fontsize": "16"}): + lb = LoadBalancers("Network Load Balancer") + aks = KubernetesServices("AKS Cluster") + nat = Internet("NAT Gateway") + + with Cluster("Node Pools", graph_attr={"fontsize": "14"}): + runtime_pool = VM("Runtime Pool\n(Standard_D4s_v3)") + data_pool = VM("Data Pool\n(Standard_D4s_v3)") + aks >> Edge(label="API Calls", fontsize="16", style="bold", color="green") >> runtime_pool + + lb >> Edge(label="API Calls", fontsize="16", style="bold", color="green") >> aks + runtime_pool >> Edge(label="Outbound", fontsize="12") >> nat + data_pool >> Edge(label="Outbound", fontsize="12") >> nat + + # GCP Resources + with Cluster("Google Cloud Platform", graph_attr={"fontsize": "20"}): + with Cluster("Apigee Organization", graph_attr={"fontsize": "16"}): + apigee = Apigee("Apigee Organization") + logging = Logging("Cloud Logging") + monitoring = Monitoring("Cloud Monitoring") + + # Connections + client >> Edge(label="API Calls", fontsize="16", style="bold", color="green") >> lb + nat >> Edge(label="Google APIs", fontsize="12") >> apigee + nat >> Edge(label="Google APIs", fontsize="12") >> logging + nat >> Edge(label="Google APIs", fontsize="12") >> monitoring diff --git a/tools/apigee-hybrid-terraform/diagram/apigee_hybrid_aks.png b/tools/apigee-hybrid-terraform/diagram/apigee_hybrid_aks.png new file mode 100644 index 000000000..615f41e6b Binary files /dev/null and b/tools/apigee-hybrid-terraform/diagram/apigee_hybrid_aks.png differ diff --git a/tools/apigee-hybrid-terraform/diagram/apigee_hybrid_gke.png b/tools/apigee-hybrid-terraform/diagram/apigee_hybrid_gke.png new file mode 100644 index 000000000..e8f7ae252 Binary files /dev/null and b/tools/apigee-hybrid-terraform/diagram/apigee_hybrid_gke.png differ diff --git a/tools/apigee-hybrid-terraform/diagram/gke_draw.py b/tools/apigee-hybrid-terraform/diagram/gke_draw.py new file mode 100644 index 000000000..eda62009c --- /dev/null +++ b/tools/apigee-hybrid-terraform/diagram/gke_draw.py @@ -0,0 +1,71 @@ +#!/bin/bash + +# Copyright 2022 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. + + +from diagrams import Diagram, Cluster, Edge +from diagrams.gcp.api import Apigee +from diagrams.gcp.compute import GKE, ComputeEngine +from diagrams.gcp.network import LoadBalancing, VPC, Router +from diagrams.onprem.client import Users +from diagrams.gcp.operations import Logging, Monitoring + +with Diagram("Apigee Hybrid on Google Kubernetes Engine", + show=False, + direction="LR", + outformat="png", + filename="apigee_hybrid_gke", + graph_attr={ + "splines": "ortho", + "nodesep": "0.8", + "ranksep": "0.8", + "pad": "0.5", + "fontsize": "45", + "fontname": "Arial", + "fontcolor": "#2D3436", + "bgcolor": "white" + }): + # Client + client = Users("Web/Mobile\nClients") + + # GCP Resources + with Cluster("Google Cloud Platform", graph_attr={"fontsize": "20"}): + with Cluster("VPC Network", graph_attr={"fontsize": "16"}): + vpc = VPC("VPC Network") + + with Cluster("GKE Cluster", graph_attr={"fontsize": "16"}): + lb = LoadBalancing("Cloud Load Balancer") + gke = GKE("GKE Cluster") + nat = Router("Cloud NAT") + + with Cluster("Node Pools", graph_attr={"fontsize": "14"}): + runtime_pool = ComputeEngine("Runtime Pool\n(e2-standard-4)") + data_pool = ComputeEngine("Data Pool\n(e2-standard-4)") + gke >> Edge(label="API Calls", fontsize="16", style="bold", color="green") >> runtime_pool + + lb >> Edge(label="API Calls", fontsize="16", style="bold", color="green") >> gke + runtime_pool >> Edge(label="Outbound", fontsize="12") >> nat + data_pool >> Edge(label="Outbound", fontsize="12") >> nat + + with Cluster("Apigee Organization", graph_attr={"fontsize": "16"}): + apigee = Apigee("Apigee Organization") + logging = Logging("Cloud Logging") + monitoring = Monitoring("Cloud Monitoring") + + # Connections + client >> Edge(label="API Calls", fontsize="16", style="bold", color="green") >> lb + nat >> Edge(label="Google APIs", fontsize="12") >> apigee + nat >> Edge(label="Google APIs", fontsize="12") >> logging + nat >> Edge(label="Google APIs", fontsize="12") >> monitoring diff --git a/tools/apigee-hybrid-terraform/pipeline.sh b/tools/apigee-hybrid-terraform/pipeline.sh new file mode 100755 index 000000000..d11f5b6aa --- /dev/null +++ b/tools/apigee-hybrid-terraform/pipeline.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# Copyright 2025 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. + +printf "Running hybrid terraform on GKE" + +cd apigee-on-gke +//Get GCP Project ID +GCP_PROJECT_ID=$(gcloud config get-value project) + +//Set GCP Project ID in terraform.tfvars +sed -i "s/project_id = \"\"/project_id = \"$GCP_PROJECT_ID\"/g" terraform.tfvars +sed -i "s/apigee_org_name = \"\"/apigee_org_name = \"$GCP_PROJECT_ID\"/g" terraform.tfvars + +terraform init +terraform plan +terraform apply --auto-approve + +#destroy terraform + +terraform destroy --auto-approve + +