├── .gitignore ├── README.md ├── main.tf ├── provider └── hcloud │ ├── cronjobs │ ├── input.tf │ ├── output.tf │ ├── provider.tf │ ├── server.tf │ └── ssh_key.tf ├── services ├── firewall │ ├── firewall.tf │ ├── input.tf │ └── output.tf ├── rancher-nginx │ ├── input.tf │ ├── nginx.tf │ ├── output.tf │ ├── rancher-v1-6.tf-backup │ ├── rancher-v2-0-configuration.tf │ └── rancher-v2-0.tf └── rancher │ ├── input.tf │ ├── output.tf │ ├── rancher-v2-0-configuration.tf │ └── rancher-v2-0.tf └── terraform.tf /.gitignore: -------------------------------------------------------------------------------- 1 | .addons 2 | .cfssl 3 | .key-pair 4 | .keypair 5 | .kube 6 | .srl 7 | .ssl 8 | .terraform 9 | 10 | terraform.tfplan 11 | terraform.tfstate* 12 | terraform.tfvars 13 | *.tfvars 14 | **/*.tfvars 15 | .terraform.tfstate.lock.info 16 | 17 | templates 18 | *.bak 19 | 20 | **/rancher_node.sh -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Setup Rancher 2.0 Cluster in Hetzner Cloud 2 | 3 | Terraform script to setup a Rancher Cluster on the Provider Hetzner Cloud using the Domain Name Services (DNS) of Zeit. 4 | 5 | [Rancher](https://rancher.com/) 6 | [Letsencrypt](https://letsencrypt.org/) 7 | [Kubernetes](https://kubernetes.io/) 8 | [Docker](https://www.docker.com/) 9 | [Hetzner](https://www.hetzner.de) 10 | [Zeit](https://zeit.co/world) 11 | [Terraform](https://www.terraform.io) 12 | 13 | * Rancher 2.0 as a Kubernetes Cluster 14 | * Letsencrypt SSL Certificate 15 | * Kubernetes Cluster with 4 nodes 16 | * Docker 1.13.1 17 | * Ubuntu 16.04.04 LTS 18 | * Firewall UFW 19 | * Network Hardening 20 | * Hetzner Cloud API 21 | * Zeit DNS 22 | * Terraform Scripts 23 | 24 | For Rancher 1.6 have a look into the backup file in the services/rancher-nginx folder. 25 | For Nginx with Letsencrypt Setup have a look into the services/rancher-nginx folder. 26 | You can use the other setup by changing the file main.tf script to the other services folder. 27 | 28 | ## Prerequisites 29 | 30 | 1. Setup [Go Language](https://golang.org/) 31 | 2. Setup [Terraform](https://www.terraform.io/downloads.html) 32 | 3. Setup [Hetzner Provider](https://github.com/hetznercloud/terraform-provider-hcloud) 33 | 4. Create Account on [Hetzner Cloud](https://www.hetzner.de/cloud) 34 | 5. Get API Token from Project [Hetzner Cloud Project](https://console.hetzner.cloud) 35 | 6. Create Account on [Zeit DNS](https://zeit.co/account) 36 | 7. Get API Token from Zeit 37 | 8. Git clone this repository 38 | 9. Cd into this repository 39 | 10. Create a new file **terraform.tfvars** with the following configuration entries: 40 | 41 | ```bash 42 | letsencrypt_mode = "--staging" 43 | rancher_cluster = "" 44 | rancher_password = "" 45 | zeit_token = "" 46 | hetzner_token = "" 47 | hetzner_user_name = "" 48 | hetzner_email = "" 49 | hetzner_ip_access = "" 50 | hetzner_ssh_key_name = "" 51 | hetzner_domain = "" 52 | hetzner_server_count = "" 53 | hetzner_server_type = "cx21" 54 | hetzner_datacenter = "nbg1-dc3" 55 | hetzner_hostname_format = "node-%03d" 56 | hetzner_image = "ubuntu-16.04" 57 | hetzner_keep_disk = "false" 58 | hetzner_backup_window = "02-06" 59 | hetzner_iso_image = "" 60 | hetzner_rescue = "" 61 | hetzner_apt_install_packages = ["python-pip","vim","software-properties-common","ufw","ceph-common","nfs-common","jq","tmux"] 62 | hetzner_apt_install_master = ["nginx","python-certbot-nginx"] 63 | ``` 64 | 65 | **Caution:** With Rancher 2.0 it uses the --acme-domain directly with the live Letsencrypt server, which means the letsencrypt rate limits apply. So, if you run the script more than 5 times, you are locked out for some time. Read [Letsencrypt's Rate Limits](https://letsencrypt.org/docs/rate-limits/). You can use the staging environment with no rate limits only in the Rancher Nginx setup, which installs the Rancher Server without acme-domain certificate, but gets a Letsencrypt Staging Certificate within Nginx. If you are ready, clear the variable **letsencrypt_mode** from --staging to empty string, and it will run on the live server to obtain a real certificate. 66 | 67 | Create the cluster with... 68 | ```bash 69 | terraform init 70 | terraform apply -auto-approve 71 | ``` 72 | 73 | After the finishing of the script, go to your URL https://node-001. and login with your new rancher password. 74 | 75 | Delete the cluster with... 76 | ```bash 77 | terraform destroy -auto-approve 78 | ``` 79 | 80 | Rancher Command Line Interface: 81 | ```bash 82 | rancher login https://node-001. -t token-abcdef 83 | ``` 84 | 85 | 86 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | module "provider" { 2 | source = "./provider/hcloud" 3 | hetzner_token = "${var.hetzner_token}" 4 | hetzner_user_name = "${var.hetzner_user_name}" 5 | hetzner_group_name = "${var.hetzner_group_name}" 6 | hetzner_ip_access = "${var.hetzner_ip_access}" 7 | hetzner_email = "${var.hetzner_email}" 8 | hetzner_ssh_key_name = "${var.hetzner_ssh_key_name}" 9 | hetzner_domain = "${var.hetzner_domain}" 10 | hetzner_server_count = "${var.hetzner_server_count}" 11 | hetzner_server_type = "${var.hetzner_server_type}" 12 | hetzner_datacenter = "${var.hetzner_datacenter}" 13 | hetzner_hostname_format = "${var.hetzner_hostname_format}" 14 | hetzner_image = "${var.hetzner_image}" 15 | hetzner_keep_disk = "${var.hetzner_keep_disk}" 16 | hetzner_backup_window = "${var.hetzner_backup_window}" 17 | hetzner_iso_image = "${var.hetzner_iso_image}" 18 | hetzner_rescue = "${var.hetzner_rescue}" 19 | hetzner_apt_install_packages = "${var.hetzner_apt_install_packages}" 20 | zeit_token = "${var.zeit_token}" 21 | } 22 | 23 | module "rancher" { 24 | source = "services/rancher" 25 | connections = "${module.provider.public_ips}" 26 | count = "${var.hetzner_server_count}" 27 | ssh_key_name = "${var.hetzner_ssh_key_name}" 28 | user = "${var.hetzner_user_name}" 29 | hostname_format = "${var.hetzner_hostname_format}" 30 | domain = "${var.hetzner_domain}" 31 | letsencrypt_mode = "${var.letsencrypt_mode}" 32 | email = "${var.hetzner_email}" 33 | rancher_password = "${var.rancher_password}" 34 | rancher_cluster = "${var.rancher_cluster}" 35 | apt_install_master = "${var.hetzner_apt_install_master}" 36 | } 37 | 38 | module "firewall" { 39 | source = "./services/firewall" 40 | connections = "${module.provider.public_ips}" 41 | count = "${var.hetzner_server_count}" 42 | ssh_key_name = "${var.hetzner_ssh_key_name}" 43 | user = "${var.hetzner_user_name}" 44 | ip_access = "${var.hetzner_ip_access}" 45 | } -------------------------------------------------------------------------------- /provider/hcloud/cronjobs: -------------------------------------------------------------------------------- 1 | # * * * * * "command to be executed" 2 | # - - - - - 3 | # | | | | | 4 | # | | | | ----- Day of week (0 - 7) (Sunday=0 or 7) 5 | # | | | ------- Month (1 - 12) 6 | # | | --------- Day of month (1 - 31) 7 | # | ----------- Hour (0 - 23) 8 | # ------------- Minute (0 - 59) 9 | # Clean up every sunday morning after 6:00am 10 | 10 6 * * 0 sudo apt-get clean 11 | 20 6 * * 0 sudo apt-get autoremove --purge 12 | 30 6 * * 0 sudo apt-get autoremove -------------------------------------------------------------------------------- /provider/hcloud/input.tf: -------------------------------------------------------------------------------- 1 | variable "hetzner_token" { 2 | description = "Hetzner Cloud Token" 3 | type = "string" 4 | } 5 | 6 | variable "hetzner_user_name" { 7 | description = "Hetzner user name on server" 8 | type = "string" 9 | } 10 | 11 | variable "hetzner_group_name" { 12 | description = "Hetzner user group name on server" 13 | type = "string" 14 | } 15 | 16 | variable "hetzner_ip_access" { 17 | description = "Hetzner IP address which is allowed to access server with ssh" 18 | type = "string" 19 | } 20 | 21 | variable "hetzner_email" { 22 | description = "Hetzner eMail of user to inform about server setup" 23 | type = "string" 24 | } 25 | 26 | variable "hetzner_ssh_key_name" { 27 | description = "Hetzner SSH Key Name for Server Logins ~/.ssh/.pub without path without file type ending" 28 | type = "string" 29 | } 30 | 31 | variable "hetzner_domain" { 32 | description = "Hetzner domain name for servers" 33 | type = "string" 34 | } 35 | 36 | variable "hetzner_server_count" { 37 | description = "Hetzner number of server to provision" 38 | type = "string" 39 | } 40 | 41 | variable "hetzner_server_type" { 42 | description = "Hetzner type of server 'cx11', 'cx21',..." 43 | type = "string" 44 | } 45 | 46 | variable "hetzner_datacenter" { 47 | description = "Hetzner datacenter name 'fsn1-dc8'=Falkenstein, 'nbg1-dc3'=Nuremberg, 'hel1-dc2'=Helsinki" 48 | type = "string" 49 | } 50 | 51 | variable "hetzner_hostname_format" { 52 | description = "Hetzner server name format" 53 | type = "string" 54 | } 55 | 56 | variable "hetzner_image" { 57 | description = "Hetzner operating system image 'ubuntu-16.04', ..." 58 | type = "string" 59 | } 60 | 61 | variable "hetzner_keep_disk" { 62 | description = "Hetzner keep disk size, to enable up- and downgrades 'true', 'false'" 63 | type = "string" 64 | } 65 | 66 | variable "hetzner_backup_window" { 67 | description = "Hetzner backup window in UTC e.g. '22-02'" 68 | type = "string" 69 | } 70 | 71 | variable "hetzner_iso_image" { 72 | description = "Hetzner iso image name to mount" 73 | type = "string" 74 | } 75 | 76 | variable "hetzner_rescue" { 77 | description = "Hetzner Enable and boot in to the specified rescue system 'linux64'" 78 | type = "string" 79 | } 80 | 81 | variable "hetzner_apt_install_packages" { 82 | description = "Hetzner applications to provision with apt-get install" 83 | type = "list" 84 | } 85 | 86 | variable "zeit_token" { 87 | description = "Zeit.co Token for administration of dns services" 88 | type = "string" 89 | } -------------------------------------------------------------------------------- /provider/hcloud/output.tf: -------------------------------------------------------------------------------- 1 | output "hostnames" { 2 | value = ["${hcloud_server.host.*.name}"] 3 | } 4 | 5 | output "public_ips" { 6 | value = ["${hcloud_server.host.*.ipv4_address}"] 7 | } 8 | 9 | output "private_ips" { 10 | value = ["${hcloud_server.host.*.ipv4_address}"] 11 | } 12 | 13 | output "private_network_interface" { 14 | value = "eth0" 15 | } -------------------------------------------------------------------------------- /provider/hcloud/provider.tf: -------------------------------------------------------------------------------- 1 | provider "hcloud" { 2 | token = "${var.hetzner_token}" 3 | } -------------------------------------------------------------------------------- /provider/hcloud/server.tf: -------------------------------------------------------------------------------- 1 | resource "hcloud_server" "host" { 2 | depends_on = ["hcloud_ssh_key.ssh_key"] 3 | 4 | count = "${var.hetzner_server_count}" 5 | 6 | name = "${format(var.hetzner_hostname_format, count.index + 1)}" 7 | datacenter = "${var.hetzner_datacenter}" 8 | image = "${var.hetzner_image}" 9 | server_type = "${var.hetzner_server_type}" 10 | ssh_keys = ["${hcloud_ssh_key.ssh_key.id}"] 11 | iso = "${var.hetzner_iso_image}" 12 | backup_window = "${var.hetzner_backup_window}" 13 | keep_disk = "${var.hetzner_keep_disk}" 14 | rescue = "${var.hetzner_rescue}" 15 | user_data = </dev/null 2>&1; do sleep 1; done", 43 | # Applications 44 | "echo 'Update Package Lists...'", 45 | "sudo apt-get -y update", 46 | "echo 'Add additional apps ...'", 47 | "while fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do sleep 1; done", 48 | "echo 'Apt Upgrade ...'", 49 | "sudo apt-get upgrade -y", 50 | "echo 'Upgrade finished...'", 51 | "while fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do sleep 1; done", 52 | "echo 'Adding to repository: ppa:certbot/certbot'", 53 | "sudo add-apt-repository -y ppa:certbot/certbot", 54 | "while fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do sleep 1; done", 55 | "echo 'Update New Package Lists...'", 56 | "sudo apt-get update -y", 57 | "while fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do sleep 1; done", 58 | "sudo echo 'Installing packages ufw unattended-upgrades sendmail ${join(" ", var.hetzner_apt_install_packages)}'", 59 | "sudo apt-get install -y ufw unattended-upgrades sendmail docker.io ${join(" ", var.hetzner_apt_install_packages)}", 60 | "while fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do sleep 1; done", 61 | 62 | # Setup cron jobs in crontab 63 | "echo 'Setting up Crontab...'", 64 | "(sudo crontab -l ; echo '${file("${path.module}/cronjobs")}') | sudo crontab -", 65 | 66 | # Setup unattended upgrades 67 | "echo 'Setting up Unattended Upgrades...'", 68 | "sudo touch /etc/apt/apt.conf.d/20auto-upgrades", 69 | "sudo echo 'APT::Periodic::Update-Package-Lists \"1\";' | sudo tee -a /etc/apt/apt.conf.d/20auto-upgrades", 70 | "sudo echo 'APT::Periodic::Download-Upgradeable-Packages \"1\";' | sudo tee -a /etc/apt/apt.conf.d/20auto-upgrades", 71 | "sudo echo 'APT::Periodic::AutocleanInterval \"3\";' | sudo tee -a /etc/apt/apt.conf.d/20auto-upgrades", 72 | "sudo echo 'APT::Periodic::Unattended-Upgrade \"1\";' | sudo tee -a /etc/apt/apt.conf.d/20auto-upgrades", 73 | "sudo chmod +w /etc/apt/apt.conf.d/50unattended-upgrades", 74 | "sudo echo 'Unattended-Upgrade::Remove-Unused-Dependencies \"true\";' | sudo tee -a /etc/apt/apt.conf.d/50unattended-upgrades", 75 | "sudo echo 'Unattended-Upgrade::Automatic-Reboot \"true\";' | sudo tee -a /etc/apt/apt.conf.d/50unattended-upgrades", 76 | "sudo echo 'Unattended-Upgrade::Automatic-Reboot-Time \"01:00\";' | sudo tee -a /etc/apt/apt.conf.d/50unattended-upgrades", 77 | "sudo echo 'Unattended-Upgrade::Mail \"${var.hetzner_email}\";' | sudo tee -a /etc/apt/apt.conf.d/50unattended-upgrades", 78 | 79 | # Setup IP Spoofing protection 80 | "echo 'Setting up IP Spoofing protection...'", 81 | "sudo echo '# IP Spoofing protection' | sudo tee -a /etc/sysctl.conf", 82 | "sudo echo 'net.ipv4.conf.all.rp_filter = 1' | sudo tee -a /etc/sysctl.conf", 83 | "sudo echo 'net.ipv4.conf.default.rp_filter = 1' | sudo tee -a /etc/sysctl.conf", 84 | "sudo echo '# Ignore ICMP broadcast requests' | sudo tee -a /etc/sysctl.conf", 85 | "sudo echo 'net.ipv4.icmp_echo_ignore_broadcasts = 1' | sudo tee -a /etc/sysctl.conf", 86 | "sudo echo '# Disable source packet routing' | sudo tee -a /etc/sysctl.conf", 87 | "sudo echo 'net.ipv4.conf.all.accept_source_route = 0' | sudo tee -a /etc/sysctl.conf", 88 | "sudo echo 'net.ipv6.conf.all.accept_source_route = 0' | sudo tee -a /etc/sysctl.conf", 89 | "sudo echo 'net.ipv4.conf.default.accept_source_route = 0' | sudo tee -a /etc/sysctl.conf", 90 | "sudo echo 'net.ipv6.conf.default.accept_source_route = 0' | sudo tee -a /etc/sysctl.conf", 91 | "sudo echo '# Ignore send redirects' | sudo tee -a /etc/sysctl.conf", 92 | "sudo echo 'net.ipv4.conf.all.send_redirects = 0' | sudo tee -a /etc/sysctl.conf", 93 | "sudo echo 'net.ipv4.conf.default.send_redirects = 0' | sudo tee -a /etc/sysctl.conf", 94 | "sudo echo '# Block SYN attacks' | sudo tee -a /etc/sysctl.conf", 95 | "sudo echo 'net.ipv4.tcp_syncookies = 1' | sudo tee -a /etc/sysctl.conf", 96 | "sudo echo 'net.ipv4.tcp_max_syn_backlog = 2048' | sudo tee -a /etc/sysctl.conf", 97 | "sudo echo 'net.ipv4.tcp_synack_retries = 2' | sudo tee -a /etc/sysctl.conf", 98 | "sudo echo 'net.ipv4.tcp_syn_retries = 5' | sudo tee -a /etc/sysctl.conf", 99 | "sudo echo '# Log Martians' | sudo tee -a /etc/sysctl.conf", 100 | "sudo echo 'net.ipv4.conf.all.log_martians = 1' | sudo tee -a /etc/sysctl.conf", 101 | "sudo echo 'net.ipv4.icmp_ignore_bogus_error_responses = 1' | sudo tee -a /etc/sysctl.conf", 102 | "sudo echo '# Ignore ICMP redirects' | sudo tee -a /etc/sysctl.conf", 103 | "sudo echo 'net.ipv4.conf.all.accept_redirects = 0' | sudo tee -a /etc/sysctl.conf", 104 | "sudo echo 'net.ipv6.conf.all.accept_redirects = 0' | sudo tee -a /etc/sysctl.conf", 105 | "sudo echo 'net.ipv4.conf.default.accept_redirects = 0' | sudo tee -a /etc/sysctl.conf", 106 | "sudo echo 'net.ipv6.conf.default.accept_redirects = 0' | sudo tee -a /etc/sysctl.conf", 107 | "sudo echo '# Ignore Directed pings' | sudo tee -a /etc/sysctl.conf", 108 | "sudo echo 'net.ipv4.icmp_echo_ignore_all = 1' | sudo tee -a /etc/sysctl.conf", 109 | 110 | # Disable IPv6 111 | "sudo echo 'net.ipv6.conf.all.disable_ipv6 = 1' | sudo tee -a /etc/sysctl.conf", 112 | "sudo echo 'net.ipv6.conf.default.disable_ipv6 = 1' | sudo tee -a /etc/sysctl.conf", 113 | "sudo echo 'net.ipv6.conf.lo.disable_ipv6 = 1' | sudo tee -a /etc/sysctl.conf", 114 | 115 | # Restart Networking 116 | "sudo sysctl -p", 117 | "cat /proc/sys/net/ipv6/conf/all/disable_ipv6", 118 | 119 | # Prevent IP Spoofing 120 | "echo 'Prevent IP Spoofing...'", 121 | "sudo sed -i 's/order hosts,bind/order bind,hosts/g' /etc/host.conf", 122 | "sudo sed -i 's/multi on/nospoof on/g' /etc/host.conf", 123 | 124 | # Adding Warning Message in the Login Banner 125 | "echo 'Setting Banners...'", 126 | "echo '!!! KEEP OUT !!! -- SYSTEM IS UNDER FULL SURVEILLANCE, WE PROSECUTE YOU DIRECTLY AND LEGALLY IN ALL CASES ---' | tee -a /etc/issue.net", 127 | "sudo sed -i 's/.*session optional pam_motd.so motd.*/# session optional pam_motd.so motd/g' /etc/pam.d/sshd", 128 | "sudo sed -i 's/.*session optional pam_motd.so noupdate.*/# session optional pam_motd.so noupdate/g' /etc/pam.d/sshd", 129 | "sudo sed -i 's/.*Banner.*/# Banner/g' /etc/ssh/sshd_config", 130 | 131 | # Restrict SSH Access 132 | "echo 'Restrict ssh access...'", 133 | "sudo sed -i 's/.*RSAAuthentication.*/RSAAuthentication yes/g' /etc/ssh/sshd_config", 134 | "sudo sed -i 's/.*PubkeyAuthentication.*/PubkeyAuthentication yes/g' /etc/ssh/sshd_config", 135 | "sudo sed -i 's/.*PasswordAuthentication.*/PasswordAuthentication no/g' /etc/ssh/sshd_config", 136 | "sudo sed -i 's/.*PermitRootLogin.*/PermitRootLogin no/g' /etc/ssh/sshd_config", 137 | "sudo echo 'AllowUsers ${var.hetzner_user_name}@${var.hetzner_ip_access}' | sudo tee -a /etc/ssh/sshd_config", 138 | "sudo service ssh restart", 139 | 140 | # Create Swapfile 141 | "echo 'Setting up Swapfile...'", 142 | "sudo fallocate -l 2G /swapfile", 143 | "sudo chmod 600 /swapfile", 144 | "sudo mkswap /swapfile", 145 | "sudo swapon /swapfile", 146 | "sudo echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab", 147 | 148 | # Secure Shared Memory 149 | "echo 'Secure Shared Memory ...'", 150 | "sudo echo 'tmpfs /run/shm tmpfs defaults,noexec,nosuid 0 0' | sudo tee -a /etc/fstab", 151 | 152 | # Bash 153 | "echo 'Setup User environment...'", 154 | "sudo cp /root/.bashrc /home/${var.hetzner_user_name}", 155 | 156 | # Rebooting 157 | "echo '....Rebooting now....'", 158 | "(sleep 2 && sudo reboot)&" 159 | 160 | ] 161 | } 162 | 163 | provisioner "local-exec" { 164 | command = </dev/null 2>&1; do sleep 1; done", 13 | "sudo apt-get install -y ${join(" ", var.apt_install_master)}", 14 | "while fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do sleep 1; done", 15 | "[ -d /var/www/html ] || sudo mkdir -p /var/www/html", 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /services/rancher-nginx/output.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobkle/terraform-rancher-hetzner/1e1908f7dc559e36d08b4bbe9a2a0a41a17f467c/services/rancher-nginx/output.tf -------------------------------------------------------------------------------- /services/rancher-nginx/rancher-v1-6.tf-backup: -------------------------------------------------------------------------------- 1 | resource "null_resource" "host" { 2 | 3 | connection { 4 | type = "ssh" 5 | host = "${element(var.connections, 0)}" 6 | user = "${var.user}" 7 | private_key = "${file("~/.ssh/${var.ssh_key_name}")}" 8 | } 9 | 10 | provisioner "remote-exec" { 11 | inline = [ 12 | "while fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do sleep 1; done", 13 | "[ -d /etc/nginx/sites-available] || sudo mkdir -p /etc/nginx/sites-available", 14 | "[ -d /etc/nginx/sites-enabled] || sudo mkdir -p /etc/nginx/sites-enabled", 15 | # "[ -d /var/www/letsencrypt/.well-known/acme-challenge ] || sudo mkdir -p /var/www/letsencrypt/.well-known/acme-challenge", 16 | "sudo docker create --name rancher-data rancher/server:stable", 17 | "sudo docker run -d --restart=unless-stopped --name=rancher-server --volumes-from rancher-data -p 127.0.0.1:8080:8080 rancher/server:stable", 18 | "sudo touch /etc/nginx/sites-available/${format(var.hostname_format, 1)}.${var.domain}", 19 | "sudo chmod a+w /etc/nginx/sites-available/${format(var.hostname_format, 1)}.${var.domain}" 20 | ] 21 | } 22 | 23 | provisioner "file" { 24 | content = </dev/null 2>&1; do sleep 1; done", 17 | "echo 'Setting up: https://${format(var.hostname_format, 1)}.${var.domain}'", 18 | 19 | # Waiting, giving the server sum time 20 | "echo 'Sleeping for 60 seconds, giving the server time to startup...'", 21 | "sleep 60", 22 | ] 23 | } 24 | 25 | provisioner "remote-exec" { 26 | inline = [ 27 | # Wait for rancher-nginx server response 28 | "while ! curl -s --insecure https://${format(var.hostname_format, 1)}.${var.domain}/ping; do sleep 5 && echo 'Still sleeping...'; done", 29 | 30 | # Login with initial default user "admin" and default password "admin" 31 | "LOGINRESPONSE=`curl -s 'https://${format(var.hostname_format, 1)}.${var.domain}/v3-public/localProviders/local?action=login' -H 'content-type: application/json' --data-binary '{\"username\":\"admin\",\"password\":\"admin\"}' --insecure`", 32 | 33 | # If the server isn't ready yet, we will receive a HTML response with e.g. Gateway, redo this request every 5s until we get a good answer in json 34 | # "while [ $LOGINRESPONSE =~ \"Gateway\" ] ; do sleep 5 && LOGINRESPONSE=`curl -s 'https://${format(var.hostname_format, 1)}.${var.domain}/v3-public/localProviders/local?action=login' -H 'content-type: application/json' --data-binary '{\"username\":\"admin\",\"password\":\"admin\"}' --insecure`; done", 35 | "echo LOGINRESPONSE=$LOGINRESPONSE", 36 | 37 | # Get the Bearer Token 38 | "LOGINTOKEN=`echo $LOGINRESPONSE | jq -r .token`", 39 | "echo LOGINTOKEN=$LOGINTOKEN", 40 | 41 | # Change the default password to my new password, which is stored in the rancher_password variable 42 | "PWCHANGE=`curl -s 'https://${format(var.hostname_format, 1)}.${var.domain}/v3/users?action=changepassword' -H 'content-type: application/json' -H \"Authorization: Bearer $LOGINTOKEN\" --data-binary '{\"currentPassword\":\"admin\",\"newPassword\":\"${var.rancher_password}\"}' --insecure`", 43 | "echo PWCHANGE=$PWCHANGE", 44 | 45 | # Create API key 46 | "APIRESPONSE=`curl -s 'https://${format(var.hostname_format, 1)}.${var.domain}/v3/token' -H 'content-type: application/json' -H \"Authorization: Bearer $LOGINTOKEN\" --data-binary '{\"type\":\"token\",\"description\":\"automation\"}' --insecure`", 47 | "echo APIRESPONSE=$APIRESPONSE", 48 | 49 | # Extract and store token 50 | "APITOKEN=`echo $APIRESPONSE | jq -r .token`", 51 | "echo APITOKEN=$APITOKEN", 52 | 53 | # Create cluster 54 | "CLUSTERRESPONSE=`curl -s 'https://${format(var.hostname_format, 1)}.${var.domain}/v3/cluster' -H 'content-type: application/json' -H \"Authorization: Bearer $LOGINTOKEN\" --data-binary '{\"type\":\"cluster\",\"nodes\":[],\"rancherKubernetesEngineConfig\":{\"ignoreDockerVersion\":true},\"name\":\"${var.rancher_cluster}\"}' --insecure`", 55 | "echo CLUSTERRESPONSE=$CLUSTERRESPONSE", 56 | 57 | # Extract clusterid to use for generating the docker run command 58 | "CLUSTERID=`echo $CLUSTERRESPONSE | jq -r .id`", 59 | "echo CLUSTERID=$CLUSTERID", 60 | 61 | # Generate docker run 62 | "AGENTIMAGE=`curl -s -H \"Authorization: Bearer $LOGINTOKEN\" https://${format(var.hostname_format, 1)}.${var.domain}/v3/settings/agent-image --insecure | jq -r .value`", 63 | "echo AGENTIMAGE=$AGENTIMAGE", 64 | 65 | "ROLEFLAGS='--etcd --controlplane --worker'", 66 | "echo ROLEFLAGS=$ROLEFLAGS", 67 | 68 | "RANCHERSERVER=\"https://${format(var.hostname_format, 1)}.${var.domain}\"", 69 | "echo RANCHERSERVER=$RANCHERSERVER", 70 | 71 | # Generate token (clusterRegistrationToken) 72 | "AGENTTOKEN=`curl -s 'https://${format(var.hostname_format, 1)}.${var.domain}/v3/clusterregistrationtoken' -H 'content-type: application/json' -H \"Authorization: Bearer $LOGINTOKEN\" --data-binary '{\"type\":\"clusterRegistrationToken\",\"clusterId\":\"'$CLUSTERID'\"}' --insecure | jq -r .token`", 73 | "echo AGENTTOKEN=$AGENTTOKEN", 74 | 75 | # Retrieve CA certificate and generate checksum 76 | "CACHECKSUM=`curl -s -H \"Authorization: Bearer $LOGINTOKEN\" https://${format(var.hostname_format, 1)}.${var.domain}}/v3/settings/cacerts --insecure | jq -r .value | sha256sum | awk '{ print $1 }'`", 77 | "echo CACHECKSUM=$CACHECKSUM", 78 | 79 | # Assemble the docker run command 80 | "AGENTCOMMAND=\"docker run -d --restart=unless-stopped -v /var/run/docker.sock:/var/run/docker.sock --net=host $AGENTIMAGE $ROLEFLAGS --server $RANCHERSERVER --token $AGENTTOKEN --ca-checksum $CACHECKSUM\"", 81 | "echo 'To connect your worker nodes with the Rancher Master node, therefore run the following command in the worker nodes shell: \n\n' $AGENTCOMMAND > /tmp/rancher_node.txt" , 82 | 83 | # Send it by email 84 | "sudo /usr/sbin/sendmail ${var.email} < /tmp/rancher_node.txt", 85 | ] 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /services/rancher-nginx/rancher-v2-0.tf: -------------------------------------------------------------------------------- 1 | resource "null_resource" "rancher" { 2 | 3 | depends_on = ["null_resource.nginx"] 4 | 5 | connection { 6 | type = "ssh" 7 | host = "${element(var.connections, 0)}" 8 | user = "${var.user}" 9 | private_key = "${file("~/.ssh/${var.ssh_key_name}")}" 10 | } 11 | 12 | provisioner "remote-exec" { 13 | inline = [ 14 | # Prepare nginx filesystem 15 | "while fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do sleep 1; done", 16 | "[ -d /etc/nginx/sites-available] || sudo mkdir -p /etc/nginx/sites-available", 17 | "[ -d /etc/nginx/sites-enabled] || sudo mkdir -p /etc/nginx/sites-enabled", 18 | "sudo touch /etc/nginx/sites-available/${format(var.hostname_format, 1)}.${var.domain}", 19 | "sudo chmod a+w /etc/nginx/sites-available/${format(var.hostname_format, 1)}.${var.domain}", 20 | "sudo ln -s /etc/nginx/sites-available/${format(var.hostname_format, count.index + 1)}.${var.domain} /etc/nginx/sites-enabled", 21 | "sudo rm /etc/nginx/sites-enabled/default", 22 | 23 | # Create Data Volume Container 24 | "sudo docker create --name rancher-data rancher/rancher:v2.0.0", 25 | 26 | # Creater Rancher v2.0.0 Container 27 | "sudo docker run -d --name rancher-server --restart=unless-stopped --volumes-from rancher-data -p 127.0.0.1:8080:80 rancher/rancher:v2.0.0", 28 | ] 29 | } 30 | 31 | # First create a simple nginx configuration to obtain letsencrypt certificate 32 | provisioner "file" { 33 | content = </dev/null 2>&1; do sleep 1; done", 17 | "echo 'Setting up: https://${format(var.hostname_format, 1)}.${var.domain}'", 18 | 19 | # Waiting, giving the server sum time 20 | "echo 'Sleeping for 60 seconds, giving the server time to startup...'", 21 | "sleep 60", 22 | ] 23 | } 24 | 25 | provisioner "remote-exec" { 26 | inline = [ 27 | # Wait for rancher-nginx server response 28 | "while ! curl -s --insecure https://${format(var.hostname_format, 1)}.${var.domain}/ping; do sleep 5 && echo 'Still sleeping...'; done", 29 | 30 | # Login with initial default user "admin" and default password "admin" 31 | "LOGINRESPONSE=`curl -s 'https://${format(var.hostname_format, 1)}.${var.domain}/v3-public/localProviders/local?action=login' -H 'content-type: application/json' --data-binary '{\"username\":\"admin\",\"password\":\"admin\"}' --insecure`", 32 | 33 | # If the server isn't ready yet, we will receive a HTML response with e.g. Gateway, redo this request every 5s until we get a good answer in json 34 | # "while [ $LOGINRESPONSE =~ \"Gateway\" ] ; do sleep 5 && LOGINRESPONSE=`curl -s 'https://${format(var.hostname_format, 1)}.${var.domain}/v3-public/localProviders/local?action=login' -H 'content-type: application/json' --data-binary '{\"username\":\"admin\",\"password\":\"admin\"}' --insecure`; done", 35 | "echo LOGINRESPONSE=$LOGINRESPONSE", 36 | 37 | # Get the Bearer Token 38 | "LOGINTOKEN=`echo $LOGINRESPONSE | jq -r .token`", 39 | "echo LOGINTOKEN=$LOGINTOKEN", 40 | 41 | # Change the default password to my new password, which is stored in the rancher_password variable 42 | "PWCHANGE=`curl -s 'https://${format(var.hostname_format, 1)}.${var.domain}/v3/users?action=changepassword' -H 'content-type: application/json' -H \"Authorization: Bearer $LOGINTOKEN\" --data-binary '{\"currentPassword\":\"admin\",\"newPassword\":\"${var.rancher_password}\"}' --insecure`", 43 | "echo PWCHANGE=$PWCHANGE", 44 | 45 | # Create API key 46 | "APIRESPONSE=`curl -s 'https://${format(var.hostname_format, 1)}.${var.domain}/v3/token' -H 'content-type: application/json' -H \"Authorization: Bearer $LOGINTOKEN\" --data-binary '{\"type\":\"token\",\"description\":\"automation\"}' --insecure`", 47 | "echo APIRESPONSE=$APIRESPONSE", 48 | 49 | # Extract and store token 50 | "APITOKEN=`echo $APIRESPONSE | jq -r .token`", 51 | "echo APITOKEN=$APITOKEN", 52 | 53 | # Create cluster 54 | "CLUSTERRESPONSE=`curl -s 'https://${format(var.hostname_format, 1)}.${var.domain}/v3/cluster' -H 'content-type: application/json' -H \"Authorization: Bearer $LOGINTOKEN\" --data-binary '{\"type\":\"cluster\",\"nodes\":[],\"rancherKubernetesEngineConfig\":{\"ignoreDockerVersion\":true},\"name\":\"${var.rancher_cluster}\"}' --insecure`", 55 | "echo CLUSTERRESPONSE=$CLUSTERRESPONSE", 56 | 57 | # Extract clusterid to use for generating the docker run command 58 | "CLUSTERID=`echo $CLUSTERRESPONSE | jq -r .id`", 59 | "echo CLUSTERID=$CLUSTERID", 60 | 61 | # Generate docker run 62 | "AGENTIMAGE=`curl -s -H \"Authorization: Bearer $LOGINTOKEN\" https://${format(var.hostname_format, 1)}.${var.domain}/v3/settings/agent-image --insecure | jq -r .value`", 63 | "echo AGENTIMAGE=$AGENTIMAGE", 64 | 65 | "ROLEFLAGS='--etcd --controlplane --worker'", 66 | "echo ROLEFLAGS=$ROLEFLAGS", 67 | 68 | "RANCHERSERVER=\"https://${format(var.hostname_format, 1)}.${var.domain}\"", 69 | "echo RANCHERSERVER=$RANCHERSERVER", 70 | 71 | # Generate token (clusterRegistrationToken) 72 | "AGENTTOKEN=`curl -s 'https://${format(var.hostname_format, 1)}.${var.domain}/v3/clusterregistrationtoken' -H 'content-type: application/json' -H \"Authorization: Bearer $LOGINTOKEN\" --data-binary '{\"type\":\"clusterRegistrationToken\",\"clusterId\":\"'$CLUSTERID'\"}' --insecure | jq -r .token`", 73 | "echo AGENTTOKEN=$AGENTTOKEN", 74 | 75 | # Retrieve CA certificate and generate checksum 76 | "CACHECKSUM=`curl -s -H \"Authorization: Bearer $LOGINTOKEN\" https://${format(var.hostname_format, 1)}.${var.domain}}/v3/settings/cacerts --insecure | jq -r .value | sha256sum | awk '{ print $1 }'`", 77 | "echo CACHECKSUM=$CACHECKSUM", 78 | 79 | # Assemble the docker run command 80 | "AGENTCOMMAND=\"docker run -d --restart=unless-stopped -v /var/run/docker.sock:/var/run/docker.sock --net=host $AGENTIMAGE $ROLEFLAGS --server $RANCHERSERVER --token $AGENTTOKEN --ca-checksum $CACHECKSUM\"", 81 | "echo '#!/bin/bash\n'$AGENTCOMMAND > /tmp/rancher_node.sh" , 82 | 83 | # Send it by email 84 | "sudo /usr/sbin/sendmail ${var.email} < /tmp/rancher_node.sh", 85 | ] 86 | } 87 | 88 | provisioner "local-exec" { 89 | command = "scp -i \"~/.ssh/${var.ssh_key_name}\" -o \"StrictHostKeyChecking=no\" -r \"${var.user}@${format(var.hostname_format, 1)}.${var.domain}:/tmp/rancher_node.sh\" \"${path.module}/rancher_node.sh\"", 90 | } 91 | } 92 | 93 | resource "null_resource" "rancher_add_nodes" { 94 | 95 | depends_on = ["null_resource.rancher_configuration"] 96 | count = "${var.count}" 97 | 98 | connection { 99 | type = "ssh" 100 | host = "${element(var.connections, count.index + 1)}" 101 | user = "${var.user}" 102 | private_key = "${file("~/.ssh/${var.ssh_key_name}")}" 103 | } 104 | 105 | provisioner "file" { 106 | source = "${path.module}/rancher_node.sh" 107 | destination = "/tmp/rancher_node.sh" 108 | } 109 | 110 | provisioner "remote-exec" { 111 | inline = [ 112 | "sudo chmod +x /tmp/rancher_node.sh", 113 | "sudo /tmp/rancher_node.sh", 114 | ] 115 | } 116 | } 117 | 118 | -------------------------------------------------------------------------------- /services/rancher/rancher-v2-0.tf: -------------------------------------------------------------------------------- 1 | resource "null_resource" "rancher" { 2 | 3 | connection { 4 | type = "ssh" 5 | host = "${element(var.connections, 0)}" 6 | user = "${var.user}" 7 | private_key = "${file("~/.ssh/${var.ssh_key_name}")}" 8 | } 9 | 10 | provisioner "remote-exec" { 11 | inline = [ 12 | # Prepare nginx filesystem 13 | "while fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do sleep 1; done", 14 | 15 | # Create Data Volume Container 16 | "sudo docker create --name rancher-data rancher/rancher:v2.0.0", 17 | 18 | # Creater Rancher v2.0.0 Container 19 | "sudo docker run -d --name rancher-server --restart=unless-stopped --volumes-from rancher-data -p 0.0.0.0:80:80 -p 0.0.0.0:443:443 rancher/rancher:v2.0.0 --acme-domain ${format(var.hostname_format, 1)}.${var.domain}", 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /terraform.tf: -------------------------------------------------------------------------------- 1 | variable "hetzner_token" { 2 | description = "Hetzner Cloud Token" 3 | type = "string" 4 | } 5 | 6 | variable "hetzner_user_name" { 7 | description = "Hetzner user name for the server, which will be created" 8 | type = "string" 9 | } 10 | 11 | variable "hetzner_group_name" { 12 | description = "Hetzner user group name for the server, which will be created" 13 | type = "string" 14 | } 15 | 16 | variable "hetzner_ip_access" { 17 | description = "Hetzner IP address which is allowed to access server with ssh" 18 | type = "string" 19 | } 20 | 21 | variable "hetzner_email" { 22 | description = "Hetzner eMail of user to inform about server setup" 23 | type = "string" 24 | } 25 | 26 | variable "hetzner_ssh_key_name" { 27 | description = "Hetzner SSH Key Name for Server Logins ~/.ssh/.pub without path without file type ending" 28 | type = "string" 29 | } 30 | 31 | variable "hetzner_domain" { 32 | description = "Hetzner domain name for servers" 33 | type = "string" 34 | } 35 | 36 | variable "hetzner_server_count" { 37 | description = "Hetzner number of server to provision" 38 | type = "string" 39 | } 40 | 41 | variable "hetzner_server_type" { 42 | description = "Hetzner type of server 'cx11', 'cx21',..." 43 | type = "string" 44 | } 45 | 46 | variable "hetzner_datacenter" { 47 | description = "Hetzner datacenter name 'fsn1-dc8'=Falkenstein, 'nbg1-dc3'=Nuremberg, 'hel1-dc2'=Helsinki" 48 | type = "string" 49 | } 50 | 51 | variable "hetzner_hostname_format" { 52 | description = "Hetzner server name format" 53 | type = "string" 54 | } 55 | 56 | variable "hetzner_image" { 57 | description = "Hetzner operating system image 'ubuntu-16.04', ..." 58 | type = "string" 59 | } 60 | 61 | variable "hetzner_keep_disk" { 62 | description = "Hetzner keep disk size, to enable up- and downgrades 'true', 'false'" 63 | type = "string" 64 | } 65 | 66 | variable "hetzner_backup_window" { 67 | description = "Hetzner backup window in UTC e.g. '22-02'" 68 | type = "string" 69 | } 70 | 71 | variable "hetzner_iso_image" { 72 | description = "Hetzner iso image name to mount" 73 | type = "string" 74 | } 75 | 76 | variable "hetzner_rescue" { 77 | description = "Hetzner Enable and boot in to the specified rescue system 'linux64'" 78 | type = "string" 79 | } 80 | 81 | variable "hetzner_apt_install_packages" { 82 | description = "Hetzner applications to provision with apt-get install" 83 | type = "list" 84 | } 85 | 86 | variable "hetzner_apt_install_master" { 87 | description = "Hetzner applications to provision additionally on master" 88 | type = "list" 89 | } 90 | 91 | variable "zeit_token" { 92 | description = "Zeit.co Token for administration of dns services" 93 | type = "string" 94 | } 95 | 96 | variable "letsencrypt_mode" { 97 | description = "Letsencrypt mode: productive='' testmode='--staging', use first --staging to not exceed rate limits during tests" 98 | type = "string" 99 | } 100 | 101 | variable "rancher_cluster" { 102 | description = "Rancher Cluster Name" 103 | type = "string" 104 | } 105 | 106 | variable "rancher_password" { 107 | description = "Rancher Cluster Password" 108 | type = "string" 109 | } --------------------------------------------------------------------------------