├── .prettierignore ├── .tool-versions ├── provider.tf ├── Taskfile.yml ├── helmfiles ├── helmfile.yaml └── subhelmfiles │ ├── cert-manager.helmfile.yaml │ └── rancher.helmfile.yaml ├── .editorconfig ├── ssh.tf ├── route53.tf ├── .github ├── CODEOWNERS └── stale.yml ├── examples └── complete │ ├── Taskfile.yml │ ├── versions.tf │ ├── terraform.tfvars │ ├── outputs.tf │ ├── variables.tf │ └── main.tf ├── rancher.tf ├── helmfile.tf ├── versions.tf ├── outputs.tf ├── .gitignore ├── .pre-commit-config.yaml ├── security_group_elb.tf ├── load_balancers.tf ├── main.tf ├── .remarkrc ├── rke.tf ├── security_group_nodes.tf ├── variables.tf ├── instances.tf ├── README.md └── LICENSE /.prettierignore: -------------------------------------------------------------------------------- 1 | helmfile.yaml 2 | *.helmfile.yaml 3 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | terraform 0.13.5 2 | terraform-docs v0.10.1 3 | tflint 0.20.3 4 | tfsec 0.36.0 5 | helmfile 0.132.1 6 | helm 3.4.0 7 | -------------------------------------------------------------------------------- /provider.tf: -------------------------------------------------------------------------------- 1 | provider "aws" {} 2 | 3 | provider "random" {} 4 | 5 | provider "tls" {} 6 | 7 | provider "rke" {} 8 | 9 | provider "rancher2" { 10 | alias = "bootstrap" 11 | } 12 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | # https://taskfile.dev 2 | 3 | version: "2" 4 | 5 | tasks: 6 | validatePreCommitHooks: 7 | desc: Validate the pre-commit hooks 8 | cmds: 9 | - pre-commit install 10 | - pre-commit run -a 11 | -------------------------------------------------------------------------------- /helmfiles/helmfile.yaml: -------------------------------------------------------------------------------- 1 | helmfiles: 2 | - path: "subhelmfiles/cert-manager-crd.helmfile.yaml" 3 | - path: "subhelmfiles/cert-manager.helmfile.yaml" 4 | - path: "subhelmfiles/rancher.helmfile.yaml" 5 | 6 | missingFileHandler: Error 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.{diff,md}] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /ssh.tf: -------------------------------------------------------------------------------- 1 | resource "tls_private_key" "ssh" { 2 | algorithm = "RSA" 3 | rsa_bits = 4096 4 | } 5 | 6 | resource "aws_key_pair" "ssh" { 7 | key_name_prefix = module.label.id 8 | public_key = tls_private_key.ssh.public_key_openssh 9 | } 10 | -------------------------------------------------------------------------------- /route53.tf: -------------------------------------------------------------------------------- 1 | resource "aws_route53_record" "rancher" { 2 | name = local.rancher_fqdn 3 | type = "A" 4 | zone_id = var.hosted_zone_id 5 | alias { 6 | evaluate_target_health = true 7 | name = aws_elb.ingress.dns_name 8 | zone_id = aws_elb.ingress.zone_id 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @saic-oss/evtr 2 | 3 | # Don't change me! Compliance team is the owner of these files 4 | /.github/ @saic-oss/compliance 5 | /.pre-commit-config.yaml @saic-oss/compliance 6 | /Taskfile.yml @saic-oss/compliance 7 | /.remarkrc @saic-oss/compliance 8 | /LICENSE @saic-oss/compliance 9 | -------------------------------------------------------------------------------- /examples/complete/Taskfile.yml: -------------------------------------------------------------------------------- 1 | # https://taskfile.dev 2 | 3 | version: "2" 4 | 5 | tasks: 6 | plan: 7 | desc: Plan the example infra 8 | cmds: 9 | - terraform plan -input=false -var-file=override.tfvars 10 | 11 | apply: 12 | desc: Apply the example infra 13 | cmds: 14 | - terraform apply -auto-approve -input=false -var-file=override.tfvars 15 | 16 | destroy: 17 | desc: Destroy the example infra 18 | cmds: 19 | - terraform destroy -auto-approve -input=false -var-file=override.tfvars 20 | -------------------------------------------------------------------------------- /examples/complete/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "2.70.0" 6 | } 7 | random = { 8 | source = "hashicorp/random" 9 | version = "3.0.0" 10 | } 11 | tls = { 12 | source = "hashicorp/tls" 13 | version = "3.0.0" 14 | } 15 | rke = { 16 | source = "rancher/rke" 17 | version = "1.1.3" 18 | } 19 | rancher2 = { 20 | source = "rancher/rancher2" 21 | version = "1.10.4" 22 | } 23 | } 24 | required_version = "0.13.5" 25 | } 26 | -------------------------------------------------------------------------------- /rancher.tf: -------------------------------------------------------------------------------- 1 | resource "random_password" "rancher_admin_pasword" { 2 | length = 16 3 | upper = true 4 | min_upper = 1 5 | lower = true 6 | min_lower = 1 7 | number = true 8 | min_numeric = 1 9 | special = true 10 | min_special = 1 11 | override_special = "_%@" 12 | } 13 | 14 | resource "rancher2_bootstrap" "admin" { 15 | provider = rancher2.bootstrap 16 | depends_on = [ 17 | null_resource.helmfile_deployments 18 | ] 19 | password = random_password.rancher_admin_pasword.result 20 | telemetry = false 21 | } 22 | -------------------------------------------------------------------------------- /helmfile.tf: -------------------------------------------------------------------------------- 1 | resource "null_resource" "helmfile_deployments" { 2 | triggers = { 3 | uuid = uuid() 4 | } 5 | provisioner "local-exec" { 6 | command = "helmfile -f ${path.module}/helmfiles/helmfile.yaml apply" 7 | environment = { 8 | KUBECONFIG = abspath(local_file.kubeconfig.filename) 9 | RANCHER_HOSTNAME = local.rancher_fqdn 10 | RANCHER_LETSENCRYPT_EMAIL = var.rancher_letsencrypt_email 11 | RANCHER_LETSENCRYPT_ENVIRONMENT = var.rancher_letsencrypt_environment 12 | } 13 | } 14 | depends_on = [ 15 | rke_cluster.default, 16 | local_file.kubeconfig 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /helmfiles/subhelmfiles/cert-manager.helmfile.yaml: -------------------------------------------------------------------------------- 1 | repositories: 2 | - name: jetstack 3 | url: "https://charts.jetstack.io" 4 | 5 | missingFileHandler: Error 6 | 7 | releases: 8 | - name: "cert-manager" 9 | namespace: "cert-manager" 10 | labels: 11 | chart: "cert-manager" 12 | repo: "jetstack" 13 | namespace: "cert-manager" 14 | vendor: "jetstack" 15 | chart: "jetstack/cert-manager" 16 | version: "1.0.1" 17 | wait: true 18 | atomic: true 19 | cleanupOnFail: true 20 | values: 21 | - resources: 22 | requests: 23 | cpu: "50m" 24 | memory: "128Mi" 25 | limits: 26 | cpu: "10" 27 | memory: "256Mi" 28 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /examples/complete/terraform.tfvars: -------------------------------------------------------------------------------- 1 | region = "us-east-1" 2 | namespace = "saic-oss" 3 | stage = "example" 4 | name = "terraform-aws-rke-rancher-master-cluster" 5 | repo = "https://github.com/saic-oss/terraform-aws-rke-rancher-master-cluster" 6 | description = "Example deployment of the Rancher Master cluster" 7 | instance_type = "t3a.medium" 8 | kubernetes_version = "v1.18.9-rancher1-1" 9 | node_volume_size = "50" 10 | availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"] 11 | subdomain_rancher_prefix = "rancher" 12 | additional_tag_map = {} 13 | rancher_letsencrypt_environment = "production" 14 | -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.13.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 2.0.0" 8 | } 9 | local = { 10 | source = "hashicorp/local" 11 | version = ">= 1.0.0" 12 | } 13 | null = { 14 | source = "hashicorp/null" 15 | version = ">= 2.0.0" 16 | } 17 | random = { 18 | source = "hashicorp/random" 19 | version = ">= 2.0.0" 20 | } 21 | template = { 22 | source = "hashicorp/template" 23 | version = ">= 2.0.0" 24 | } 25 | tls = { 26 | source = "hashicorp/tls" 27 | version = ">= 2.0.0" 28 | } 29 | rke = { 30 | source = "rancher/rke" 31 | version = ">= 1.0.0" 32 | } 33 | rancher2 = { 34 | source = "rancher/rancher2" 35 | version = ">= 1.0.0" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /helmfiles/subhelmfiles/rancher.helmfile.yaml: -------------------------------------------------------------------------------- 1 | repositories: 2 | - name: rancher 3 | url: "https://releases.rancher.com/server-charts/stable" 4 | 5 | missingFileHandler: Error 6 | 7 | releases: 8 | - name: "rancher" 9 | namespace: "cattle-system" 10 | labels: 11 | chart: "rancher" 12 | repo: "rancher-stable" 13 | namespace: "cattle-system" 14 | vendor: "rancher" 15 | chart: "rancher/rancher" 16 | version: "2.4.8" 17 | wait: true 18 | atomic: true 19 | cleanupOnFail: true 20 | values: 21 | - ingress: 22 | tls: 23 | source: "letsEncrypt" 24 | hostname: {{ requiredEnv "RANCHER_HOSTNAME" | quote }} 25 | letsEncrypt: 26 | email: {{ requiredEnv "RANCHER_LETSENCRYPT_EMAIL" | quote }} 27 | environment: 28 | {{ requiredEnv "RANCHER_LETSENCRYPT_ENVIRONMENT" | quote }} 29 | resources: 30 | requests: 31 | cpu: "100m" 32 | memory: "1Gi" 33 | limits: 34 | cpu: "1" 35 | memory: "1Gi" 36 | -------------------------------------------------------------------------------- /examples/complete/outputs.tf: -------------------------------------------------------------------------------- 1 | output "ssh_public_key" { 2 | description = "Cluster nodes' public SSH key" 3 | value = module.rke_rancher_master_cluster.ssh_public_key 4 | } 5 | 6 | output "ssh_private_key" { 7 | description = "Cluster nodes' private SSH key" 8 | value = module.rke_rancher_master_cluster.ssh_private_key 9 | sensitive = true 10 | } 11 | 12 | output "cluster_kubeconfig" { 13 | description = "KUBECONFIG yaml file contents to connect to the cluster. DO NOT USE unless you have no other options. Users should use the KUBECONFIG that Rancher provides to them rather than this." 14 | value = module.rke_rancher_master_cluster.cluster_kubeconfig 15 | sensitive = true 16 | } 17 | 18 | output "rancher_endpoint" { 19 | description = "Endpoint of Rancher Server" 20 | value = module.rke_rancher_master_cluster.rancher_endpoint 21 | } 22 | 23 | output "rancher_admin_password" { 24 | description = "Password for Rancher 'admin' user" 25 | value = module.rke_rancher_master_cluster.rancher_admin_password 26 | sensitive = true 27 | } 28 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "ssh_public_key" { 2 | description = "Cluster nodes' public SSH key" 3 | value = tls_private_key.ssh.public_key_openssh 4 | } 5 | 6 | output "ssh_private_key" { 7 | description = "Cluster nodes' private SSH key" 8 | value = tls_private_key.ssh.private_key_pem 9 | sensitive = true 10 | } 11 | 12 | output "cluster_kubeconfig" { 13 | description = "KUBECONFIG yaml file contents to connect to the cluster. DO NOT USE unless you have no other options. Users should use the KUBECONFIG that Rancher provides to them rather than this." 14 | value = rke_cluster.default.kube_config_yaml 15 | sensitive = true 16 | } 17 | 18 | output "rancher_endpoint" { 19 | description = "Endpoint of Rancher Server" 20 | value = "https://${var.subdomain_rancher}.${var.hosted_zone_domain_name}" 21 | } 22 | 23 | output "rancher_admin_password" { 24 | description = "Password for Rancher 'admin' user" 25 | value = random_password.rancher_admin_pasword.result 26 | sensitive = true 27 | } 28 | 29 | output "rancher_admin_token" { 30 | description = "API Token for Rancher 'admin' user" 31 | value = rancher2_bootstrap.admin.token 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ######################### 2 | # TERRAFORM 3 | ######################### 4 | 5 | # Local .terraform directories 6 | **/.terraform/* 7 | 8 | # .tfstate files 9 | *.tfstate 10 | *.tfstate.* 11 | 12 | # Crash log files 13 | crash.log 14 | 15 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 16 | # .tfvars files are managed as part of configuration and so should be included in 17 | # version control. 18 | # 19 | # example.tfvars 20 | 21 | # Ignore override files as they are usually used to override resources locally and so 22 | # are not checked in 23 | override.tf 24 | override.tf.json 25 | *_override.tf 26 | *_override.tf.json 27 | override.tfvars 28 | 29 | # Include override files you do wish to add to version control using negated pattern 30 | # 31 | # !example_override.tf 32 | 33 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 34 | # example: *tfplan* 35 | 36 | ######################### 37 | # IntelliJ IDEA 38 | ######################### 39 | 40 | .idea 41 | 42 | ######################### 43 | # MacOS 44 | ######################### 45 | 46 | .DS_Store 47 | 48 | ######################### 49 | # Project Specific 50 | ######################### 51 | 52 | /tmp 53 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/antonbabenko/pre-commit-terraform 3 | rev: v1.43.0 4 | hooks: 5 | - id: terraform_fmt 6 | - id: terraform_docs 7 | - id: terraform_tflint 8 | - id: terraform_tfsec 9 | - repo: https://github.com/pre-commit/pre-commit-hooks 10 | rev: v3.2.0 11 | hooks: 12 | - id: check-added-large-files 13 | args: ["--maxkb=500"] 14 | - id: check-case-conflict 15 | - id: check-json 16 | - id: check-merge-conflict 17 | - id: check-xml 18 | - id: check-yaml 19 | args: ["--unsafe"] 20 | - id: detect-aws-credentials 21 | args: ["--allow-missing-credentials"] 22 | - id: detect-private-key 23 | - id: end-of-file-fixer 24 | exclude: "^.idea/.*$" 25 | - id: forbid-new-submodules 26 | - id: mixed-line-ending 27 | - id: no-commit-to-branch 28 | args: [--branch, master] 29 | - id: trailing-whitespace 30 | args: [--markdown-linebreak-ext=md] 31 | - repo: https://github.com/gruntwork-io/pre-commit 32 | rev: v0.1.10 33 | hooks: 34 | - id: shellcheck 35 | - repo: https://github.com/prettier/prettier 36 | rev: 2.1.2 37 | hooks: 38 | - id: prettier 39 | -------------------------------------------------------------------------------- /security_group_elb.tf: -------------------------------------------------------------------------------- 1 | resource "aws_security_group" "ingress_elb" { 2 | name = "${module.label.id}-ingress-elb" 3 | description = "Security group for the ingress ELB" 4 | vpc_id = var.vpc_id 5 | tags = module.label.tags 6 | } 7 | 8 | resource "aws_security_group_rule" "ingress_elb_egress" { 9 | description = "Allow all egress traffic" 10 | from_port = 0 11 | to_port = 0 12 | protocol = "-1" 13 | cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:AWS007 14 | security_group_id = aws_security_group.ingress_elb.id 15 | type = "egress" 16 | } 17 | 18 | resource "aws_security_group_rule" "ingress_elb_port_http" { 19 | description = "Allow HTTP traffic from everywhere" 20 | from_port = 80 21 | to_port = 80 22 | protocol = "TCP" 23 | cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:AWS006 24 | security_group_id = aws_security_group.ingress_elb.id 25 | type = "ingress" 26 | } 27 | 28 | resource "aws_security_group_rule" "ingress_elb_port_https" { 29 | description = "Allow HTTPS traffic from everywhere" 30 | from_port = 443 31 | to_port = 443 32 | protocol = "TCP" 33 | cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:AWS006 34 | security_group_id = aws_security_group.ingress_elb.id 35 | type = "ingress" 36 | } 37 | -------------------------------------------------------------------------------- /load_balancers.tf: -------------------------------------------------------------------------------- 1 | resource "random_uuid" "aws_elb_postfix" {} 2 | 3 | resource "aws_elb" "ingress" { #tfsec:ignore:AWS005 4 | 5 | // The format of the name here is complicated because the max length of ELB names is 32 chars and 6 | // using name_prefix adds 26 chars to the end, leaving only 6 chars for the prefix. This line is saying 7 | // that the prefix can be up to 24 chars and the rest gets a truncated uuid 8 | name = substr(format("%s-%s", format("%.23s", module.label.id), replace(random_uuid.aws_elb_postfix.result, "-", "")), 0, 32) 9 | 10 | subnets = distinct([var.node_group_1_subnet_id, var.node_group_2_subnet_id, var.node_group_3_subnet_id]) 11 | security_groups = [aws_security_group.ingress_elb.id] 12 | listener { 13 | instance_port = 80 14 | instance_protocol = "tcp" 15 | lb_port = 80 16 | lb_protocol = "tcp" 17 | } 18 | listener { 19 | instance_port = 443 20 | instance_protocol = "tcp" 21 | lb_port = 443 22 | lb_protocol = "tcp" 23 | } 24 | health_check { 25 | healthy_threshold = 2 26 | interval = 5 27 | target = "HTTP:80/healthz" 28 | timeout = 2 29 | unhealthy_threshold = 2 30 | } 31 | instances = concat(tolist(aws_instance.node_group_1.*.id), tolist(aws_instance.node_group_2.*.id), tolist(aws_instance.node_group_3.*.id)) 32 | idle_timeout = 1800 33 | 34 | tags = module.label.tags 35 | } 36 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | rancher_fqdn = "${var.subdomain_rancher}.${var.hosted_zone_domain_name}" 3 | 4 | // Most of these should eventually get moved to variables, but they are staying hard coded for now for simplicity. 5 | ssh_user = "ubuntu" 6 | node_group_1_count = 1 7 | node_group_2_count = 1 8 | node_group_3_count = 1 9 | node_group_1_ami = "ami-05801d0a3c8e4c443" // Ubuntu Bionic 18.04 10 | node_group_2_ami = "ami-05801d0a3c8e4c443" // Ubuntu Bionic 18.04 11 | node_group_3_ami = "ami-05801d0a3c8e4c443" // Ubuntu Bionic 18.04 12 | instance_user_data = < /etc/docker/daemon.json 23 | - systemctl restart docker && systemctl enable docker 24 | EOF 25 | } 26 | 27 | module "label" { 28 | source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.17.0" 29 | namespace = var.namespace 30 | stage = var.stage 31 | name = var.name 32 | additional_tag_map = var.additional_tag_map 33 | 34 | tags = { 35 | "Repo" = "${var.repo}", 36 | "Owner" = "${var.owner}", 37 | "Description" = "${var.description}" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.remarkrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "remark-lint-file-extension", 4 | "remark-lint-no-file-name-mixed-case", 5 | "remark-lint-no-file-name-articles", 6 | "remark-lint-no-file-name-irregular-characters", 7 | "remark-lint-no-file-name-consecutive-dashes", 8 | "remark-lint-no-file-name-outer-dashes", 9 | "remark-lint-no-consecutive-blank-lines", 10 | "remark-lint-hard-break-spaces", 11 | ["remark-lint-heading-style", "atx"], 12 | "remark-lint-heading-increment", 13 | "remark-lint-no-multiple-toplevel-headings", 14 | ["remark-lint-blockquote-indentation", 2], 15 | "remark-lint-no-blockquote-without-marker", 16 | ["remark-lint-unordered-list-marker-style", "-"], 17 | ["remark-lint-ordered-list-marker-style", "."], 18 | ["remark-lint-ordered-list-marker-value", "one"], 19 | ["remark-lint-list-item-indent", "space"], 20 | "remark-lint-list-item-content-indent", 21 | ["remark-lint-code-block-style", "fenced"], 22 | ["remark-lint-fenced-code-flag", {"allowEmpty": false}], 23 | ["remark-lint-fenced-code-marker", "`"], 24 | ["remark-lint-rule-style", "---"], 25 | "remark-lint-no-table-indentation", 26 | "remark-lint-table-pipes", 27 | ["remark-lint-table-cell-padding", "padded"], 28 | "remark-lint-no-inline-padding", 29 | "remark-lint-no-shortcut-reference-image", 30 | "remark-lint-no-shortcut-reference-link", 31 | "remark-lint-final-definition", 32 | "remark-lint-definition-case", 33 | "remark-lint-definition-spacing", 34 | ["remark-lint-link-title-style", "\""], 35 | ["remark-lint-strong-marker", "*"], 36 | ["remark-lint-emphasis-marker", "*"], 37 | "remark-lint-no-emphasis-as-heading", 38 | "remark-lint-no-literal-urls", 39 | "remark-lint-no-auto-link-without-protocol" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /rke.tf: -------------------------------------------------------------------------------- 1 | resource "rke_cluster" "default" { 2 | dynamic nodes { 3 | for_each = aws_instance.node_group_1 4 | content { 5 | address = nodes.value.public_ip 6 | internal_address = nodes.value.private_ip 7 | user = local.ssh_user 8 | role = ["controlplane", "etcd", "worker"] 9 | ssh_key = tls_private_key.ssh.private_key_pem 10 | } 11 | } 12 | 13 | dynamic nodes { 14 | for_each = aws_instance.node_group_2 15 | content { 16 | address = nodes.value.public_ip 17 | internal_address = nodes.value.private_ip 18 | user = local.ssh_user 19 | role = ["controlplane", "etcd", "worker"] 20 | ssh_key = tls_private_key.ssh.private_key_pem 21 | } 22 | } 23 | 24 | dynamic nodes { 25 | for_each = aws_instance.node_group_3 26 | content { 27 | address = nodes.value.public_ip 28 | internal_address = nodes.value.private_ip 29 | user = local.ssh_user 30 | role = ["controlplane", "etcd", "worker"] 31 | ssh_key = tls_private_key.ssh.private_key_pem 32 | } 33 | } 34 | 35 | cluster_name = module.label.id 36 | kubernetes_version = var.kubernetes_version 37 | depends_on = [ 38 | aws_instance.node_group_1, 39 | aws_instance.node_group_2, 40 | aws_instance.node_group_3, 41 | aws_elb.ingress, 42 | aws_route53_record.rancher, 43 | aws_security_group.ingress_elb, 44 | aws_security_group_rule.ingress_elb_egress, 45 | aws_security_group_rule.ingress_elb_port_http, 46 | aws_security_group_rule.ingress_elb_port_https, 47 | aws_security_group.nodes, 48 | aws_security_group_rule.nodes_egress, 49 | aws_security_group_rule.nodes_ingress_self, 50 | aws_security_group_rule.nodes_ingress_ssh, 51 | aws_security_group_rule.nodes_ingress_k8s, 52 | aws_security_group_rule.nodes_ingress_elb_80, 53 | aws_security_group_rule.nodes_ingress_elb_443, 54 | tls_private_key.ssh, 55 | aws_key_pair.ssh 56 | ] 57 | } 58 | 59 | resource "local_file" "kubeconfig" { 60 | filename = "${path.module}/tmp/kubeconfig.yaml" 61 | sensitive_content = rke_cluster.default.kube_config_yaml 62 | file_permission = "0644" 63 | depends_on = [ 64 | rke_cluster.default 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /security_group_nodes.tf: -------------------------------------------------------------------------------- 1 | resource "aws_security_group" "nodes" { 2 | name = "${module.label.id}-nodes" 3 | description = "Security group for RKE nodes" 4 | vpc_id = var.vpc_id 5 | tags = module.label.tags 6 | } 7 | 8 | resource "aws_security_group_rule" "nodes_egress" { 9 | description = "Allow all egress traffic" 10 | from_port = 0 11 | to_port = 0 12 | protocol = "-1" 13 | cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:AWS007 14 | security_group_id = aws_security_group.nodes.id 15 | type = "egress" 16 | } 17 | 18 | resource "aws_security_group_rule" "nodes_ingress_self" { 19 | description = "Allow all intra-security-group traffic" 20 | from_port = 0 21 | to_port = 0 22 | protocol = "-1" 23 | self = true 24 | security_group_id = aws_security_group.nodes.id 25 | type = "ingress" 26 | } 27 | 28 | resource "aws_security_group_rule" "nodes_ingress_ssh" { 29 | description = "Allow all SSH traffic" 30 | from_port = 22 31 | to_port = 22 32 | protocol = "TCP" 33 | cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:AWS006 34 | security_group_id = aws_security_group.nodes.id 35 | type = "ingress" 36 | } 37 | 38 | resource "aws_security_group_rule" "nodes_ingress_k8s" { 39 | description = "Allow all k8s API traffic" 40 | from_port = 6443 41 | to_port = 6443 42 | protocol = "TCP" 43 | cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:AWS006 44 | security_group_id = aws_security_group.nodes.id 45 | type = "ingress" 46 | } 47 | 48 | resource "aws_security_group_rule" "nodes_ingress_elb_80" { 49 | description = "Allow HTTP traffic from the ELB" 50 | from_port = 80 51 | to_port = 80 52 | protocol = "TCP" 53 | source_security_group_id = aws_security_group.ingress_elb.id 54 | security_group_id = aws_security_group.nodes.id 55 | type = "ingress" 56 | } 57 | 58 | resource "aws_security_group_rule" "nodes_ingress_elb_443" { 59 | description = "Allow HTTPS traffic from the ELB" 60 | from_port = 443 61 | to_port = 443 62 | protocol = "TCP" 63 | source_security_group_id = aws_security_group.ingress_elb.id 64 | security_group_id = aws_security_group.nodes.id 65 | type = "ingress" 66 | } 67 | -------------------------------------------------------------------------------- /examples/complete/variables.tf: -------------------------------------------------------------------------------- 1 | variable "namespace" { 2 | type = string 3 | description = "Namespace, which could be your organization name or abbreviation" 4 | } 5 | 6 | variable "stage" { 7 | type = string 8 | description = "Stage, e.g. 'prod', 'staging', 'dev'" 9 | } 10 | 11 | variable "name" { 12 | type = string 13 | description = "Solution name" 14 | } 15 | 16 | variable "repo" { 17 | type = string 18 | description = "Repo URL that is responsible for this resource" 19 | } 20 | 21 | variable "owner" { 22 | type = string 23 | description = "Email address of owner" 24 | } 25 | 26 | variable "description" { 27 | type = string 28 | description = "Short description of what/why this product exists" 29 | } 30 | 31 | variable "region" { 32 | type = string 33 | description = "AWS region to deploy to" 34 | } 35 | 36 | variable "instance_type" { 37 | type = string 38 | description = "Instance type to use for the cluster nodes" 39 | } 40 | 41 | variable "additional_tag_map" { 42 | type = map(string) 43 | description = "Map of additional tags to apply to every taggable resource. If you don't want any use an empty map - '{}'" 44 | } 45 | 46 | variable "node_volume_size" { 47 | type = string 48 | description = "Volume size of worker node disk in GB" 49 | } 50 | 51 | variable "kubernetes_version" { 52 | type = string 53 | description = "Kubernetes version to use. Must be supported by the version of the RKE provider you are using. See https://github.com/rancher/terraform-provider-rke/releases" 54 | } 55 | 56 | variable "hosted_zone_id" { 57 | type = string 58 | description = "ID of Route53 hosted zone to create records in" 59 | } 60 | 61 | variable "hosted_zone_domain_name" { 62 | type = string 63 | description = "Domain name of the hosted zone to create records in" 64 | } 65 | 66 | variable "subdomain_rancher_prefix" { 67 | type = string 68 | description = "Rancher's endpoint will be '{subdomain_rancher}.{hosted_zone}'. {subdomain_rancher} can be multi-layered e.g. 'rancher.foo.bar'. A random pet name will be added on for deconfliction" 69 | } 70 | 71 | variable "availability_zones" { 72 | type = list(string) 73 | description = "AZs to deploy to" 74 | } 75 | 76 | variable "rancher_letsencrypt_email" { 77 | type = string 78 | description = "Email address to use for Rancher's LetsEncrypt certificate" 79 | } 80 | 81 | variable "rancher_letsencrypt_environment" { 82 | type = string 83 | description = "LetsEncrypt environment to use - Valid options: 'staging', 'production'" 84 | } 85 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "namespace" { 2 | type = string 3 | description = "Namespace, which could be your organization name or abbreviation" 4 | } 5 | 6 | variable "stage" { 7 | type = string 8 | description = "Stage, e.g. 'prod', 'staging', 'dev'" 9 | } 10 | 11 | variable "name" { 12 | type = string 13 | description = "Solution name" 14 | } 15 | 16 | variable "repo" { 17 | type = string 18 | description = "Repo URL that is responsible for this resource" 19 | } 20 | 21 | variable "owner" { 22 | type = string 23 | description = "Email address of owner" 24 | } 25 | 26 | variable "description" { 27 | type = string 28 | description = "Short description of what/why this product exists" 29 | } 30 | 31 | variable "additional_tag_map" { 32 | type = map(string) 33 | description = "Map of additional tags to apply to every taggable resource. If you don't want any use an empty map - '{}'" 34 | } 35 | 36 | variable "instance_type" { 37 | type = string 38 | description = "Instance type to use for the cluster nodes" 39 | } 40 | 41 | variable "vpc_id" { 42 | type = string 43 | description = "ID of the VPC to deploy to" 44 | } 45 | 46 | variable "node_group_1_subnet_id" { 47 | type = string 48 | description = "Subnet to deploy node group 1 to" 49 | } 50 | 51 | variable "node_group_2_subnet_id" { 52 | type = string 53 | description = "Subnet to deploy node group 2 to" 54 | } 55 | 56 | variable "node_group_3_subnet_id" { 57 | type = string 58 | description = "Subnet to deploy node group 3 to" 59 | } 60 | 61 | variable "node_volume_size" { 62 | type = string 63 | description = "Volume size of worker node disk in GB" 64 | } 65 | 66 | variable "kubernetes_version" { 67 | type = string 68 | description = "Kubernetes version to use. Must be supported by the version of the RKE provider you are using. See [https://github.com/rancher/terraform-provider-rke/releases](https://github.com/rancher/terraform-provider-rke/releases)" 69 | } 70 | 71 | variable "hosted_zone_id" { 72 | type = string 73 | description = "ID of Route53 hosted zone to create records in" 74 | } 75 | 76 | variable "hosted_zone_domain_name" { 77 | type = string 78 | description = "Domain name of the hosted zone to create records in" 79 | } 80 | 81 | variable "subdomain_rancher" { 82 | type = string 83 | description = "Rancher's endpoint will be '{subdomain_rancher}.{hosted_zone_domain_name}'. {subdomain_rancher} can be multi-layered e.g. 'rancher.foo.bar'" 84 | } 85 | 86 | variable "rancher_letsencrypt_email" { 87 | type = string 88 | description = "Email address to use for Rancher's LetsEncrypt certificate" 89 | } 90 | 91 | variable "rancher_letsencrypt_environment" { 92 | type = string 93 | description = "LetsEncrypt environment to use - Valid options: 'staging', 'production'" 94 | } 95 | -------------------------------------------------------------------------------- /examples/complete/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.region 3 | } 4 | 5 | provider "random" {} 6 | 7 | provider "tls" {} 8 | 9 | provider "rke" { 10 | debug = true 11 | } 12 | 13 | provider "rancher2" { 14 | alias = "bootstrap" 15 | api_url = local.rancher_endpoint 16 | insecure = true #should be false in production! 17 | bootstrap = true 18 | } 19 | 20 | resource "random_pet" "default" { 21 | length = 2 22 | prefix = "example" 23 | separator = "-" 24 | } 25 | 26 | locals { 27 | subdomain_rancher_with_entropy = "${var.subdomain_rancher_prefix}.${random_pet.default.id}" 28 | rancher_endpoint = "https://${local.subdomain_rancher_with_entropy}.${var.hosted_zone_domain_name}" 29 | } 30 | 31 | module "vpc" { 32 | source = "git::https://github.com/cloudposse/terraform-aws-vpc.git?ref=tags/0.16.1" 33 | namespace = var.namespace 34 | stage = var.stage 35 | name = var.name 36 | cidr_block = "172.16.0.0/16" 37 | } 38 | 39 | module "subnets" { 40 | source = "git::https://github.com/cloudposse/terraform-aws-dynamic-subnets.git?ref=tags/0.31.0" 41 | availability_zones = var.availability_zones 42 | namespace = var.namespace 43 | stage = var.stage 44 | name = var.name 45 | vpc_id = module.vpc.vpc_id 46 | igw_id = module.vpc.igw_id 47 | cidr_block = module.vpc.vpc_cidr_block 48 | nat_gateway_enabled = false 49 | nat_instance_enabled = false 50 | depends_on = [ 51 | module.vpc 52 | ] 53 | } 54 | 55 | module "rke_rancher_master_cluster" { 56 | //source = "git::https://path/to/repo.git?ref=tags/x.y.z" 57 | source = "../.." 58 | additional_tag_map = {} 59 | description = var.description 60 | instance_type = var.instance_type 61 | kubernetes_version = var.kubernetes_version 62 | name = var.name 63 | namespace = var.namespace 64 | node_group_1_subnet_id = module.subnets.public_subnet_ids[0] 65 | node_group_2_subnet_id = module.subnets.public_subnet_ids[1] 66 | node_group_3_subnet_id = module.subnets.public_subnet_ids[2] 67 | node_volume_size = var.node_volume_size 68 | owner = var.owner 69 | repo = var.repo 70 | stage = var.stage 71 | vpc_id = module.vpc.vpc_id 72 | hosted_zone_id = var.hosted_zone_id 73 | hosted_zone_domain_name = var.hosted_zone_domain_name 74 | subdomain_rancher = local.subdomain_rancher_with_entropy 75 | rancher_letsencrypt_email = var.rancher_letsencrypt_email 76 | rancher_letsencrypt_environment = var.rancher_letsencrypt_environment 77 | depends_on = [ 78 | module.vpc, 79 | module.subnets 80 | ] 81 | providers = { 82 | aws = aws 83 | random = random 84 | tls = tls 85 | rke = rke 86 | rancher2.bootstrap = rancher2.bootstrap 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /instances.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "node_group_1" { 2 | count = local.node_group_1_count 3 | ami = local.node_group_1_ami 4 | instance_type = var.instance_type 5 | key_name = aws_key_pair.ssh.id 6 | user_data = local.instance_user_data 7 | vpc_security_group_ids = [aws_security_group.nodes.id] 8 | subnet_id = var.node_group_1_subnet_id 9 | associate_public_ip_address = true #tfsec:ignore:AWS012 10 | ebs_optimized = true 11 | root_block_device { 12 | volume_type = "gp2" 13 | volume_size = var.node_volume_size 14 | } 15 | tags = merge(module.label.tags, 16 | { 17 | "Name" = "${module.label.id}-rancher-master-1.${count.index}" 18 | }) 19 | provisioner "remote-exec" { 20 | connection { 21 | host = self.public_ip 22 | user = local.ssh_user 23 | private_key = tls_private_key.ssh.private_key_pem 24 | } 25 | inline = [ 26 | "echo 'Waiting for cloud-init to complete...'", 27 | "cloud-init status --wait > /dev/null", 28 | "echo 'Completed cloud-init!'" 29 | ] 30 | } 31 | } 32 | 33 | resource "aws_instance" "node_group_2" { 34 | count = local.node_group_2_count 35 | ami = local.node_group_2_ami 36 | instance_type = var.instance_type 37 | key_name = aws_key_pair.ssh.id 38 | user_data = local.instance_user_data 39 | vpc_security_group_ids = [aws_security_group.nodes.id] 40 | subnet_id = var.node_group_2_subnet_id 41 | associate_public_ip_address = true #tfsec:ignore:AWS012 42 | ebs_optimized = true 43 | root_block_device { 44 | volume_type = "gp2" 45 | volume_size = var.node_volume_size 46 | } 47 | tags = merge(module.label.tags, 48 | { 49 | "Name" = "${module.label.id}-rancher-master-2.${count.index}" 50 | }) 51 | provisioner "remote-exec" { 52 | connection { 53 | host = self.public_ip 54 | user = local.ssh_user 55 | private_key = tls_private_key.ssh.private_key_pem 56 | } 57 | inline = [ 58 | "echo 'Waiting for cloud-init to complete...'", 59 | "cloud-init status --wait > /dev/null", 60 | "echo 'Completed cloud-init!'" 61 | ] 62 | } 63 | } 64 | 65 | resource "aws_instance" "node_group_3" { 66 | count = local.node_group_3_count 67 | ami = local.node_group_3_ami 68 | instance_type = var.instance_type 69 | key_name = aws_key_pair.ssh.id 70 | user_data = local.instance_user_data 71 | vpc_security_group_ids = [aws_security_group.nodes.id] 72 | subnet_id = var.node_group_3_subnet_id 73 | associate_public_ip_address = true #tfsec:ignore:AWS012 74 | ebs_optimized = true 75 | root_block_device { 76 | volume_type = "gp2" 77 | volume_size = var.node_volume_size 78 | } 79 | tags = merge(module.label.tags, 80 | { 81 | "Name" = "${module.label.id}-rancher-master-3.${count.index}" 82 | }) 83 | provisioner "remote-exec" { 84 | connection { 85 | host = self.public_ip 86 | user = local.ssh_user 87 | private_key = tls_private_key.ssh.private_key_pem 88 | } 89 | inline = [ 90 | "echo 'Waiting for cloud-init to complete...'", 91 | "cloud-init status --wait > /dev/null", 92 | "echo 'Completed cloud-init!'" 93 | ] 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # terraform-aws-rke-rancher-master-cluster 2 | 3 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/1a0da89e2ec443ed9de6d90eaa9bccd8)](https://www.codacy.com/gh/saic-oss/terraform-aws-rke-rancher-master-cluster/dashboard?utm_source=github.com&utm_medium=referral&utm_content=saic-oss/terraform-aws-rke-rancher-master-cluster&utm_campaign=Badge_Grade) 4 | 5 | Terraform module that creates an RKE cluster, meant to serve as nothing but a highly-available Rancher "master" cluster 6 | 7 | ## Introduction 8 | 9 | ### Purpose 10 | 11 | The purpose of this module is to give an easy way to stand up a production-ready Rancher "master" cluster. It is intended to be a "turn-key" module, so it includes (almost) everything needed to have Rancher up and running, including the AWS compute infrastructure, Kubernetes cluster, load balancer, Route53 DNS entry, and the Rancher deployment itself. 12 | 13 | ### High-level design 14 | 15 | #### Resources provisioned 16 | 17 | - [x] 3 "node groups" of EC2 instances - gives you the ability to upgrade the AMI of one node group at a time so you can do an in-place upgrade 18 | - Does not use AutoScalingGroups (yet) - There's a bit of "chicken and egg" problem with the initial standup of a Rancher Server cluster. Worker clusters can use ASGs, but it isn't as easy to dynamically join instances to the master cluster 19 | - Currently creates Ubuntu nodes with Docker installed since that is what others that have come before have done, but the desire is to switch to CentOS with optional use of Red Hat Enterprise Linux (RHEL) because of its greater support for automated security tools that are commonly used in the federal government. 20 | - [x] A Kubernetes cluster installed on the EC2 instances 21 | - Uses the Terraform RKE provider 22 | - Labels all nodes with `["controlplane", "etcd", "worker"]` - Remember this cluster should be used as the Rancher master cluster and nothing else 23 | - [x] A Classic Load Balancer (ELB) with listeners on port 80 and port 443 that points to port 80 and 443 of the cluster nodes 24 | - [x] 2 Security Groups 25 | - The `nodes` security group is used by the EC2 instances and allows: 26 | - Any traffic inside its own security group 27 | - SSH traffic from anywhere 28 | - K8s API traffic from anywhere 29 | - Traffic on ports 80 and 443 from the `elb` security group 30 | - The `elb` security group is used by the load balancer and allows: 31 | - Traffic on ports 80 and 443 from anywhere 32 | - [x] An AWS Key Pair with a new TLS private key 33 | - [x] A Route53 record that configures a dnsName to point at the ELB 34 | - [x] Uses a `local-exec` to `helmfile apply` CertManager and Rancher Server 35 | 36 | ### Limitations 37 | 38 | 1. At the moment, this module cannot be deployed to private subnets. Deploying to private subnets can be added later if desired. 39 | 40 | ## Usage 41 | 42 | ### Prerequisites 43 | 44 | 1. Terraform v0.13+ - Uses the new way to pull down 3rd party providers. 45 | 1. \*nix operating system - Windows not supported. If you need to use this on Windows you can run it from a Docker container. 46 | 1. Since this module uses a `local-exec`, the following tools also need to be installed on the machine using this module: 47 | 1. [kubectl][kubectl] 48 | 1. [helm][helm] 49 | 1. [helmfile][helmfile] 50 | 1. [helm-diff plugin][helm-diff] 51 | 52 | ### Instructions 53 | 54 | #### Complete Example 55 | 56 | See [examples/complete](examples/complete) for an example of how to use this module. For your convenience a Taskfile has been provided to be used with [go-task][go-task]. 57 | 58 | ```sh 59 | cd examples/complete 60 | task plan 61 | task apply 62 | task destroy 63 | ``` 64 | 65 | > There are a few parameters that are specific to your AWS account and your domain name you want to use that are not included in the example `terraform.tfvars`. You should create a `override.tfvars` file and add the missing parameters to that. 66 | 67 | #### Provider config 68 | 69 | This module uses provider aliases, so you have to explicitly pass in provider configurations. Here's a minimum example: 70 | 71 | ```hcl 72 | provider "aws" { 73 | region = var.region 74 | } 75 | 76 | provider "random" {} 77 | 78 | provider "tls" {} 79 | 80 | provider "rke" { 81 | debug = true 82 | } 83 | 84 | provider "rancher2" { 85 | alias = "bootstrap" 86 | api_url = "https://${var.subdomain_rancher}.${var.hosted_zone}" 87 | insecure = false 88 | bootstrap = true 89 | } 90 | 91 | module "rke_rancher_master_cluster" { 92 | source = "git::https://path/to/repo.git?ref=tags/x.y.z" 93 | additional_tag_map = {} 94 | instance_type = var.instance_type 95 | kubernetes_version = var.kubernetes_version 96 | name = var.name 97 | namespace = var.namespace 98 | node_group_1_subnet_id = var.node_group_1_subnet_id 99 | node_group_2_subnet_id = var.node_group_2_subnet_id 100 | node_group_3_subnet_id = var.node_group_3_subnet_id 101 | node_volume_size = var.node_volume_size 102 | stage = var.stage 103 | vpc_id = var.vpc_id 104 | hosted_zone = var.hosted_zone 105 | subdomain_rancher = var.subdomain_rancher 106 | rancher_letsencrypt_email = var.rancher_letsencrypt_email 107 | rancher_letsencrypt_environment = var.rancher_letsencrypt_environment 108 | providers = { 109 | aws = aws 110 | random = random 111 | tls = tls 112 | rke = rke 113 | rancher2.bootstrap = rancher2.bootstrap 114 | } 115 | } 116 | ``` 117 | 118 | #### Logging into Rancher 119 | 120 | The module outputs variables `rancher_endpoint` and `rancher_admin_password`. The username is `admin`. The admin password is managed by Terraform, don't change it manually. 121 | 122 | ## Contributing 123 | 124 | Contributors to this module should make themselves familiar with this section. 125 | 126 | ### Prerequisites 127 | 128 | - Terraform v0.13+ 129 | - [pre-commit][pre-commit] 130 | - Pre-commit hook dependencies 131 | - nodejs (for the prettier hook) 132 | - [tflint][tflint] 133 | - [terraform-docs][terraform-docs] 134 | - [tfsec][tfsec] 135 | - Run `pre-commit install` in root dir of repo (installs the pre-commit hooks so they run automatically when you try to do a git commit) 136 | 137 | Using the [ASDF][asdf] version manager is highly encouraged. The project supports it by using a `.tool-versions` file to specify the versions of tools used and ensure that all necessary tools are installed. 138 | 139 | See [this Gist][add-asdf-plugins.sh] for a quick way to add a set of plugins that will work for this project 140 | 141 | ### Versioning 142 | 143 | This module will use SemVer, and will stay on v0.X for the foreseeable future 144 | 145 | 146 | 147 | ## Requirements 148 | 149 | | Name | Version | 150 | |------|---------| 151 | | terraform | >= 0.13.0 | 152 | | aws | >= 2.0.0 | 153 | | local | >= 1.0.0 | 154 | | null | >= 2.0.0 | 155 | | rancher2 | >= 1.0.0 | 156 | | random | >= 2.0.0 | 157 | | rke | >= 1.0.0 | 158 | | template | >= 2.0.0 | 159 | | tls | >= 2.0.0 | 160 | 161 | ## Providers 162 | 163 | | Name | Version | 164 | |------|---------| 165 | | aws | >= 2.0.0 | 166 | | local | >= 1.0.0 | 167 | | null | >= 2.0.0 | 168 | | rancher2.bootstrap | >= 1.0.0 | 169 | | random | >= 2.0.0 | 170 | | rke | >= 1.0.0 | 171 | | tls | >= 2.0.0 | 172 | 173 | ## Inputs 174 | 175 | | Name | Description | Type | Default | Required | 176 | |------|-------------|------|---------|:--------:| 177 | | additional\_tag\_map | Map of additional tags to apply to every taggable resource. If you don't want any use an empty map - '{}' | `map(string)` | n/a | yes | 178 | | description | Short description of what/why this product exists | `string` | n/a | yes | 179 | | hosted\_zone\_domain\_name | Domain name of the hosted zone to create records in | `string` | n/a | yes | 180 | | hosted\_zone\_id | ID of Route53 hosted zone to create records in | `string` | n/a | yes | 181 | | instance\_type | Instance type to use for the cluster nodes | `string` | n/a | yes | 182 | | kubernetes\_version | Kubernetes version to use. Must be supported by the version of the RKE provider you are using. See [https://github.com/rancher/terraform-provider-rke/releases](https://github.com/rancher/terraform-provider-rke/releases) | `string` | n/a | yes | 183 | | name | Solution name | `string` | n/a | yes | 184 | | namespace | Namespace, which could be your organization name or abbreviation | `string` | n/a | yes | 185 | | node\_group\_1\_subnet\_id | Subnet to deploy node group 1 to | `string` | n/a | yes | 186 | | node\_group\_2\_subnet\_id | Subnet to deploy node group 2 to | `string` | n/a | yes | 187 | | node\_group\_3\_subnet\_id | Subnet to deploy node group 3 to | `string` | n/a | yes | 188 | | node\_volume\_size | Volume size of worker node disk in GB | `string` | n/a | yes | 189 | | owner | Email address of owner | `string` | n/a | yes | 190 | | rancher\_letsencrypt\_email | Email address to use for Rancher's LetsEncrypt certificate | `string` | n/a | yes | 191 | | rancher\_letsencrypt\_environment | LetsEncrypt environment to use - Valid options: 'staging', 'production' | `string` | n/a | yes | 192 | | repo | Repo URL that is responsible for this resource | `string` | n/a | yes | 193 | | stage | Stage, e.g. 'prod', 'staging', 'dev' | `string` | n/a | yes | 194 | | subdomain\_rancher | Rancher's endpoint will be '{subdomain\_rancher}.{hosted\_zone\_domain\_name}'. {subdomain\_rancher} can be multi-layered e.g. 'rancher.foo.bar' | `string` | n/a | yes | 195 | | vpc\_id | ID of the VPC to deploy to | `string` | n/a | yes | 196 | 197 | ## Outputs 198 | 199 | | Name | Description | 200 | |------|-------------| 201 | | cluster\_kubeconfig | KUBECONFIG yaml file contents to connect to the cluster. DO NOT USE unless you have no other options. Users should use the KUBECONFIG that Rancher provides to them rather than this. | 202 | | rancher\_admin\_password | Password for Rancher 'admin' user | 203 | | rancher\_admin\_token | API Token for Rancher 'admin' user | 204 | | rancher\_endpoint | Endpoint of Rancher Server | 205 | | ssh\_private\_key | Cluster nodes' private SSH key | 206 | | ssh\_public\_key | Cluster nodes' public SSH key | 207 | 208 | 209 | 210 | 211 | [asdf]: https://asdf-vm.com 212 | [pre-commit]: https://pre-commit.com/ 213 | [tflint]: https://github.com/terraform-linters/tflint 214 | [terraform-docs]: https://github.com/terraform-docs/terraform-docs 215 | [tfsec]: https://github.com/liamg/tfsec 216 | [kubectl]: https://kubernetes.io/docs/tasks/tools/install-kubectl/ 217 | [helm]: https://helm.sh/docs/intro/install/ 218 | [helmfile]: https://github.com/roboll/helmfile 219 | [helm-diff]: https://github.com/databus23/helm-diff 220 | [go-task]: https://taskfile.dev 221 | [add-asdf-plugins.sh]: https://gist.github.com/RothAndrew/fadd2f613d4b09cca3df69e848e3a504 222 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------