You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

801 lines
33 KiB

  1. # Kubernetes on OpenStack with Terraform
  2. Provision a Kubernetes cluster with [Terraform](https://www.terraform.io) on
  3. OpenStack.
  4. ## Status
  5. This will install a Kubernetes cluster on an OpenStack Cloud. It should work on
  6. most modern installs of OpenStack that support the basic services.
  7. ### Known compatible public clouds
  8. - [Auro](https://auro.io/)
  9. - [Betacloud](https://www.betacloud.io/)
  10. - [CityCloud](https://www.citycloud.com/)
  11. - [DreamHost](https://www.dreamhost.com/cloud/computing/)
  12. - [ELASTX](https://elastx.se/)
  13. - [EnterCloudSuite](https://www.entercloudsuite.com/)
  14. - [FugaCloud](https://fuga.cloud/)
  15. - [Open Telekom Cloud](https://cloud.telekom.de/)
  16. - [OVH](https://www.ovh.com/)
  17. - [Rackspace](https://www.rackspace.com/)
  18. - [Safespring](https://www.safespring.com)
  19. - [Ultimum](https://ultimum.io/)
  20. - [VexxHost](https://vexxhost.com/)
  21. - [Zetta](https://www.zetta.io/)
  22. - [Cloudify](https://www.cloudify.ro/en)
  23. ## Approach
  24. The terraform configuration inspects variables found in
  25. [variables.tf](variables.tf) to create resources in your OpenStack cluster.
  26. There is a [python script](../terraform.py) that reads the generated`.tfstate`
  27. file to generate a dynamic inventory that is consumed by the main ansible script
  28. to actually install kubernetes and stand up the cluster.
  29. ### Networking
  30. The configuration includes creating a private subnet with a router to the
  31. external net. It will allocate floating IPs from a pool and assign them to the
  32. hosts where that makes sense. You have the option of creating bastion hosts
  33. inside the private subnet to access the nodes there. Alternatively, a node with
  34. a floating IP can be used as a jump host to nodes without.
  35. #### Using an existing router
  36. It is possible to use an existing router instead of creating one. To use an
  37. existing router set the router\_id variable to the uuid of the router you wish
  38. to use.
  39. For example:
  40. ```ShellSession
  41. router_id = "00c542e7-6f46-4535-ae95-984c7f0391a3"
  42. ```
  43. ### Kubernetes Nodes
  44. You can create many different kubernetes topologies by setting the number of
  45. different classes of hosts. For each class there are options for allocating
  46. floating IP addresses or not.
  47. - Control plane nodes with etcd
  48. - Control plane nodes without etcd
  49. - Standalone etcd hosts
  50. - Kubernetes worker nodes
  51. Note that the Ansible script will report an invalid configuration if you wind up
  52. with an even number of etcd instances since that is not a valid configuration. This
  53. restriction includes standalone etcd nodes that are deployed in a cluster along with
  54. control plane nodes with etcd replicas. As an example, if you have three control plane
  55. nodes with etcd replicas and three standalone etcd nodes, the script will fail since
  56. there are now six total etcd replicas.
  57. ### GlusterFS shared file system
  58. The Terraform configuration supports provisioning of an optional GlusterFS
  59. shared file system based on a separate set of VMs. To enable this, you need to
  60. specify:
  61. - the number of Gluster hosts (minimum 2)
  62. - Size of the non-ephemeral volumes to be attached to store the GlusterFS bricks
  63. - Other properties related to provisioning the hosts
  64. Even if you are using Flatcar Container Linux by Kinvolk for your cluster, you will still
  65. need the GlusterFS VMs to be based on either Debian or RedHat based images.
  66. Flatcar Container Linux by Kinvolk cannot serve GlusterFS, but can connect to it through
  67. binaries available on hyperkube v1.4.3_coreos.0 or higher.
  68. ## Requirements
  69. - [Install Terraform](https://www.terraform.io/intro/getting-started/install.html) 0.14 or later
  70. - [Install Ansible](http://docs.ansible.com/ansible/latest/intro_installation.html)
  71. - you already have a suitable OS image in Glance
  72. - you already have a floating IP pool created
  73. - you have security groups enabled
  74. - you have a pair of keys generated that can be used to secure the new hosts
  75. ## Module Architecture
  76. The configuration is divided into four modules:
  77. - Network
  78. - Loadbalancer
  79. - IPs
  80. - Compute
  81. The main reason for splitting the configuration up in this way is to easily
  82. accommodate situations where floating IPs are limited by a quota or if you have
  83. any external references to the floating IP (e.g. DNS) that would otherwise have
  84. to be updated.
  85. You can force your existing IPs by modifying the compute variables in
  86. `kubespray.tf` as follows:
  87. ```ini
  88. k8s_master_fips = ["151.101.129.67"]
  89. k8s_node_fips = ["151.101.129.68"]
  90. ```
  91. ## Terraform
  92. Terraform will be used to provision all of the OpenStack resources with base software as appropriate.
  93. ### Configuration
  94. #### Inventory files
  95. Create an inventory directory for your cluster by copying the existing sample and linking the `hosts` script (used to build the inventory based on Terraform state):
  96. ```ShellSession
  97. cp -LRp contrib/terraform/openstack/sample-inventory inventory/$CLUSTER
  98. cd inventory/$CLUSTER
  99. ln -s ../../contrib/terraform/openstack/hosts
  100. ln -s ../../contrib
  101. ```
  102. This will be the base for subsequent Terraform commands.
  103. #### OpenStack access and credentials
  104. No provider variables are hardcoded inside `variables.tf` because Terraform
  105. supports various authentication methods for OpenStack: the older script and
  106. environment method (using `openrc`) as well as a newer declarative method, and
  107. different OpenStack environments may support Identity API version 2 or 3.
  108. These are examples and may vary depending on your OpenStack cloud provider,
  109. for an exhaustive list on how to authenticate on OpenStack with Terraform
  110. please read the [OpenStack provider documentation](https://www.terraform.io/docs/providers/openstack/).
  111. ##### Declarative method (recommended)
  112. The recommended authentication method is to describe credentials in a YAML file `clouds.yaml` that can be stored in:
  113. - the current directory
  114. - `~/.config/openstack`
  115. - `/etc/openstack`
  116. `clouds.yaml`:
  117. ```yaml
  118. clouds:
  119. mycloud:
  120. auth:
  121. auth_url: https://openstack:5000/v3
  122. username: "username"
  123. project_name: "projectname"
  124. project_id: projectid
  125. user_domain_name: "Default"
  126. password: "password"
  127. region_name: "RegionOne"
  128. interface: "public"
  129. identity_api_version: 3
  130. ```
  131. If you have multiple clouds defined in your `clouds.yaml` file you can choose
  132. the one you want to use with the environment variable `OS_CLOUD`:
  133. ```ShellSession
  134. export OS_CLOUD=mycloud
  135. ```
  136. ##### Openrc method
  137. When using classic environment variables, Terraform uses default `OS_*`
  138. environment variables. A script suitable for your environment may be available
  139. from Horizon under *Project* -> *Compute* -> *Access & Security* -> *API Access*.
  140. With identity v2:
  141. ```ShellSession
  142. source openrc
  143. env | grep OS
  144. OS_AUTH_URL=https://openstack:5000/v2.0
  145. OS_PROJECT_ID=projectid
  146. OS_PROJECT_NAME=projectname
  147. OS_USERNAME=username
  148. OS_PASSWORD=password
  149. OS_REGION_NAME=RegionOne
  150. OS_INTERFACE=public
  151. OS_IDENTITY_API_VERSION=2
  152. ```
  153. With identity v3:
  154. ```ShellSession
  155. source openrc
  156. env | grep OS
  157. OS_AUTH_URL=https://openstack:5000/v3
  158. OS_PROJECT_ID=projectid
  159. OS_PROJECT_NAME=username
  160. OS_PROJECT_DOMAIN_ID=default
  161. OS_USERNAME=username
  162. OS_PASSWORD=password
  163. OS_REGION_NAME=RegionOne
  164. OS_INTERFACE=public
  165. OS_IDENTITY_API_VERSION=3
  166. OS_USER_DOMAIN_NAME=Default
  167. ```
  168. Terraform does not support a mix of DomainName and DomainID, choose one or the other:
  169. - provider.openstack: You must provide exactly one of DomainID or DomainName to authenticate by Username
  170. ```ShellSession
  171. unset OS_USER_DOMAIN_NAME
  172. export OS_USER_DOMAIN_ID=default
  173. ```
  174. or
  175. ```ShellSession
  176. unset OS_PROJECT_DOMAIN_ID
  177. set OS_PROJECT_DOMAIN_NAME=Default
  178. ```
  179. #### Cluster variables
  180. The construction of the cluster is driven by values found in
  181. [variables.tf](variables.tf).
  182. For your cluster, edit `inventory/$CLUSTER/cluster.tfvars`.
  183. |Variable | Description |
  184. |---------|-------------|
  185. |`cluster_name` | All OpenStack resources will use the Terraform variable`cluster_name` (default`example`) in their name to make it easier to track. For example the first compute resource will be named`example-kubernetes-1`. |
  186. |`az_list` | List of Availability Zones available in your OpenStack cluster. |
  187. |`network_name` | The name to be given to the internal network that will be generated |
  188. |`use_existing_network`| Use an existing network with the name of `network_name`. `false` by default |
  189. |`network_dns_domain` | (Optional) The dns_domain for the internal network that will be generated |
  190. |`dns_nameservers`| An array of DNS name server names to be used by hosts in the internal subnet. |
  191. |`floatingip_pool` | Name of the pool from which floating IPs will be allocated |
  192. |`k8s_master_fips` | A list of floating IPs that you have already pre-allocated; they will be attached to master nodes instead of creating new random floating IPs. |
  193. |`bastion_fips` | A list of floating IPs that you have already pre-allocated; they will be attached to bastion node instead of creating new random floating IPs. |
  194. |`external_net` | UUID of the external network that will be routed to |
  195. |`flavor_k8s_master`,`flavor_k8s_node`,`flavor_etcd`, `flavor_bastion`,`flavor_gfs_node` | Flavor depends on your openstack installation, you can get available flavor IDs through `openstack flavor list` |
  196. |`image`,`image_gfs`, `image_master` | Name of the image to use in provisioning the compute resources. Should already be loaded into glance. |
  197. |`image_uuid`,`image_gfs_uuid`, `image_master_uuid` | UUID of the image to use in provisioning the compute resources. Should already be loaded into glance. |
  198. |`ssh_user`,`ssh_user_gfs` | The username to ssh into the image with. This usually depends on the image you have selected |
  199. |`public_key_path` | Path on your local workstation to the public key file you wish to use in creating the key pairs |
  200. |`number_of_k8s_masters`, `number_of_k8s_masters_no_floating_ip` | Number of nodes that serve as both master and etcd. These can be provisioned with or without floating IP addresses|
  201. |`number_of_k8s_masters_no_etcd`, `number_of_k8s_masters_no_floating_ip_no_etcd` | Number of nodes that serve as just master with no etcd. These can be provisioned with or without floating IP addresses |
  202. |`number_of_etcd` | Number of pure etcd nodes |
  203. |`number_of_k8s_nodes`, `number_of_k8s_nodes_no_floating_ip` | Kubernetes worker nodes. These can be provisioned with or without floating ip addresses. |
  204. |`number_of_bastions` | Number of bastion hosts to create. Scripts assume this is really just zero or one |
  205. |`number_of_gfs_nodes_no_floating_ip` | Number of gluster servers to provision. |
  206. | `gfs_volume_size_in_gb` | Size of the non-ephemeral volumes to be attached to store the GlusterFS bricks |
  207. |`supplementary_master_groups` | To add ansible groups to the masters, such as `kube_node` for tainting them as nodes, empty by default. |
  208. |`supplementary_node_groups` | To add ansible groups to the nodes, such as `kube_ingress` for running ingress controller pods, empty by default. |
  209. |`bastion_allowed_remote_ips` | List of CIDR allowed to initiate a SSH connection, `["0.0.0.0/0"]` by default |
  210. |`bastion_allowed_remote_ipv6_ips` | List of IPv6 CIDR allowed to initiate a SSH connection, `["::/0"]` by default |
  211. |`master_allowed_remote_ips` | List of CIDR blocks allowed to initiate an API connection, `["0.0.0.0/0"]` by default |
  212. |`master_allowed_remote_ipv6_ips` | List of IPv6 CIDR blocks allowed to initiate an API connection, `["::/0"]` by default |
  213. |`bastion_allowed_ports` | List of ports to open on bastion node, `[]` by default |
  214. |`bastion_allowed_ports_ipv6` | List of ports to open on bastion node for IPv6 CIDR blocks, `[]` by default |
  215. |`k8s_allowed_remote_ips` | List of CIDR allowed to initiate a SSH connection, empty by default |
  216. |`k8s_allowed_remote_ips_ipv6` | List of IPv6 CIDR allowed to initiate a SSH connection, empty by default |
  217. |`k8s_allowed_egress_ipv6_ips` | List of IPv6 CIDRs allowed for egress traffic, `["::/0"]` by default |
  218. |`worker_allowed_ports` | List of ports to open on worker nodes, `[{ "protocol" = "tcp", "port_range_min" = 30000, "port_range_max" = 32767, "remote_ip_prefix" = "0.0.0.0/0"}]` by default |
  219. |`worker_allowed_ports_ipv6` | List of ports to open on worker nodes for IPv6 CIDR blocks, `[{ "protocol" = "tcp", "port_range_min" = 30000, "port_range_max" = 32767, "remote_ip_prefix" = "::/0"}]` by default |
  220. |`master_allowed_ports` | List of ports to open on master nodes, expected format is `[{ "protocol" = "tcp", "port_range_min" = 443, "port_range_max" = 443, "remote_ip_prefix" = "0.0.0.0/0"}]`, empty by default |
  221. |`master_allowed_ports_ipv6` | List of ports to open on master nodes for IPv6 CIDR blocks, expected format is `[{ "protocol" = "tcp", "port_range_min" = 443, "port_range_max" = 443, "remote_ip_prefix" = "::/0"}]`, empty by default |
  222. |`node_root_volume_size_in_gb` | Size of the root volume for nodes, 0 to use ephemeral storage |
  223. |`master_root_volume_size_in_gb` | Size of the root volume for masters, 0 to use ephemeral storage |
  224. |`master_volume_type` | Volume type of the root volume for control_plane, 'Default' by default |
  225. |`node_volume_type` | Volume type of the root volume for nodes, 'Default' by default |
  226. |`gfs_root_volume_size_in_gb` | Size of the root volume for gluster, 0 to use ephemeral storage |
  227. |`etcd_root_volume_size_in_gb` | Size of the root volume for etcd nodes, 0 to use ephemeral storage |
  228. |`bastion_root_volume_size_in_gb` | Size of the root volume for bastions, 0 to use ephemeral storage |
  229. |`master_server_group_policy` | Enable and use openstack nova servergroups for masters with set policy, default: "" (disabled) |
  230. |`node_server_group_policy` | Enable and use openstack nova servergroups for nodes with set policy, default: "" (disabled) |
  231. |`etcd_server_group_policy` | Enable and use openstack nova servergroups for etcd with set policy, default: "" (disabled) |
  232. |`additional_server_groups` | Extra server groups to create. Set "policy" to the policy for the group, expected format is `{"new-server-group" = {"policy" = "anti-affinity"}}`, default: {} (to not create any extra groups) |
  233. |`use_access_ip` | If 1, nodes with floating IPs will transmit internal cluster traffic via floating IPs; if 0 private IPs will be used instead. Default value is 1. |
  234. |`port_security_enabled` | Allow to disable port security by setting this to `false`. `true` by default |
  235. |`force_null_port_security` | Set `null` instead of `true` or `false` for `port_security`. `false` by default |
  236. |`k8s_nodes` | Map containing worker node definition, see explanation below |
  237. |`k8s_masters` | Map containing master node definition, see explanation for k8s_nodes and `sample-inventory/cluster.tfvars` |
  238. |`k8s_master_loadbalancer_enabled` | Enable and use an Octavia load balancer for the K8s master nodes |
  239. |`k8s_master_loadbalancer_listener_port` | Define via which port the K8s Api should be exposed. `6443` by default |
  240. |`k8s_master_loadbalancer_server_port` | Define via which port the K8S api is available on the master nodes. `6443` by default |
  241. |`k8s_master_loadbalancer_public_ip` | Specify if an existing floating IP should be used for the load balancer. A new floating IP is assigned by default |
  242. ##### k8s_nodes
  243. Allows a custom definition of worker nodes giving the operator full control over individual node flavor and availability zone placement.
  244. To enable the use of this mode set the `number_of_k8s_nodes` and `number_of_k8s_nodes_no_floating_ip` variables to 0.
  245. Then define your desired worker node configuration using the `k8s_nodes` variable.
  246. The `az`, `flavor` and `floating_ip` parameters are mandatory.
  247. The optional parameter `extra_groups` (a comma-delimited string) can be used to define extra inventory group memberships for specific nodes.
  248. ```yaml
  249. k8s_nodes:
  250. node-name:
  251. az: string # Name of the AZ
  252. flavor: string # Flavor ID to use
  253. floating_ip: bool # If floating IPs should be used or not
  254. reserved_floating_ip: string # If floating_ip is true use existing floating IP, if reserved_floating_ip is an empty string and floating_ip is true, a new floating IP will be created
  255. extra_groups: string # (optional) Additional groups to add for kubespray, defaults to no groups
  256. image_id: string # (optional) Image ID to use, defaults to var.image_id or var.image
  257. root_volume_size_in_gb: number # (optional) Size of the block storage to use as root disk, defaults to var.node_root_volume_size_in_gb or to use volume from flavor otherwise
  258. volume_type: string # (optional) Volume type to use, defaults to var.node_volume_type
  259. network_id: string # (optional) Use this network_id for the node, defaults to either var.network_id or ID of var.network_name
  260. server_group: string # (optional) Server group to add this node to. If set, this has to be one specified in additional_server_groups, defaults to use the server group specified in node_server_group_policy
  261. cloudinit: # (optional) Options for cloud-init
  262. extra_partitions: # List of extra partitions (other than the root partition) to setup during creation
  263. volume_path: string # Path to the volume to create partition for (e.g. /dev/vda )
  264. partition_path: string # Path to the partition (e.g. /dev/vda2 )
  265. mount_path: string # Path to where the partition should be mounted
  266. partition_start: string # Where the partition should start (e.g. 10GB ). Note, if you set the partition_start to 0 there will be no space left for the root partition
  267. partition_end: string # Where the partition should end (e.g. 10GB or -1 for end of volume)
  268. netplan_critical_dhcp_interface: string # Name of interface to set the dhcp flag critical = true, to circumvent [this issue](https://bugs.launchpad.net/ubuntu/+source/systemd/+bug/1776013).
  269. ```
  270. For example:
  271. ```ini
  272. k8s_nodes = {
  273. "1" = {
  274. "az" = "sto1"
  275. "flavor" = "83d8b44a-26a0-4f02-a981-079446926445"
  276. "floating_ip" = true
  277. },
  278. "2" = {
  279. "az" = "sto2"
  280. "flavor" = "83d8b44a-26a0-4f02-a981-079446926445"
  281. "floating_ip" = true
  282. },
  283. "3" = {
  284. "az" = "sto3"
  285. "flavor" = "83d8b44a-26a0-4f02-a981-079446926445"
  286. "floating_ip" = true
  287. "extra_groups" = "calico_rr"
  288. }
  289. }
  290. ```
  291. Would result in the same configuration as:
  292. ```ini
  293. number_of_k8s_nodes = 3
  294. flavor_k8s_node = "83d8b44a-26a0-4f02-a981-079446926445"
  295. az_list = ["sto1", "sto2", "sto3"]
  296. ```
  297. And:
  298. ```ini
  299. k8s_nodes = {
  300. "ing-1" = {
  301. "az" = "sto1"
  302. "flavor" = "83d8b44a-26a0-4f02-a981-079446926445"
  303. "floating_ip" = true
  304. },
  305. "ing-2" = {
  306. "az" = "sto2"
  307. "flavor" = "83d8b44a-26a0-4f02-a981-079446926445"
  308. "floating_ip" = true
  309. },
  310. "ing-3" = {
  311. "az" = "sto3"
  312. "flavor" = "83d8b44a-26a0-4f02-a981-079446926445"
  313. "floating_ip" = true
  314. },
  315. "big-1" = {
  316. "az" = "sto1"
  317. "flavor" = "3f73fc93-ec61-4808-88df-2580d94c1a9b"
  318. "floating_ip" = false
  319. },
  320. "big-2" = {
  321. "az" = "sto2"
  322. "flavor" = "3f73fc93-ec61-4808-88df-2580d94c1a9b"
  323. "floating_ip" = false
  324. },
  325. "big-3" = {
  326. "az" = "sto3"
  327. "flavor" = "3f73fc93-ec61-4808-88df-2580d94c1a9b"
  328. "floating_ip" = false
  329. },
  330. "small-1" = {
  331. "az" = "sto1"
  332. "flavor" = "7a6a998f-ac7f-4fb8-a534-2175b254f75e"
  333. "floating_ip" = false
  334. },
  335. "small-2" = {
  336. "az" = "sto2"
  337. "flavor" = "7a6a998f-ac7f-4fb8-a534-2175b254f75e"
  338. "floating_ip" = false
  339. },
  340. "small-3" = {
  341. "az" = "sto3"
  342. "flavor" = "7a6a998f-ac7f-4fb8-a534-2175b254f75e"
  343. "floating_ip" = false
  344. }
  345. }
  346. ```
  347. Would result in three nodes in each availability zone each with their own separate naming,
  348. flavor and floating ip configuration.
  349. The "schema":
  350. ```ini
  351. k8s_nodes = {
  352. "key | node name suffix, must be unique" = {
  353. "az" = string
  354. "flavor" = string
  355. "floating_ip" = bool
  356. },
  357. }
  358. ```
  359. All values are required.
  360. #### Terraform state files
  361. In the cluster's inventory folder, the following files might be created (either by Terraform
  362. or manually), to prevent you from pushing them accidentally they are in a
  363. `.gitignore` file in the `terraform/openstack` directory :
  364. - `.terraform`
  365. - `.tfvars`
  366. - `.tfstate`
  367. - `.tfstate.backup`
  368. You can still add them manually if you want to.
  369. ### Initialization
  370. Before Terraform can operate on your cluster you need to install the required
  371. plugins. This is accomplished as follows:
  372. ```ShellSession
  373. cd inventory/$CLUSTER
  374. terraform -chdir="../../contrib/terraform/openstack" init
  375. ```
  376. This should finish fairly quickly telling you Terraform has successfully initialized and loaded necessary modules.
  377. ### Customizing with cloud-init
  378. You can apply cloud-init based customization for the openstack instances before provisioning your cluster.
  379. One common template is used for all instances. Adjust the file shown below:
  380. `contrib/terraform/openstack/modules/compute/templates/cloudinit.yaml.tmpl`
  381. For example, to enable openstack novnc access and ansible_user=root SSH access:
  382. ```ShellSession
  383. #cloud-config
  384. ## in some cases novnc console access is required
  385. ## it requires ssh password to be set
  386. ssh_pwauth: yes
  387. chpasswd:
  388. list: |
  389. root:secret
  390. expire: False
  391. ## in some cases direct root ssh access via ssh key is required
  392. disable_root: false
  393. ```
  394. ### Provisioning cluster
  395. You can apply the Terraform configuration to your cluster with the following command
  396. issued from your cluster's inventory directory (`inventory/$CLUSTER`):
  397. ```ShellSession
  398. terraform -chdir="../../contrib/terraform/openstack" apply -var-file=cluster.tfvars
  399. ```
  400. if you chose to create a bastion host, this script will create
  401. `contrib/terraform/openstack/k8s_cluster.yml` with an ssh command for Ansible to
  402. be able to access your machines tunneling through the bastion's IP address. If
  403. you want to manually handle the ssh tunneling to these machines, please delete
  404. or move that file. If you want to use this, just leave it there, as ansible will
  405. pick it up automatically.
  406. ### Destroying cluster
  407. You can destroy your new cluster with the following command issued from the cluster's inventory directory:
  408. ```ShellSession
  409. terraform -chdir="../../contrib/terraform/openstack" destroy -var-file=cluster.tfvars
  410. ```
  411. If you've started the Ansible run, it may also be a good idea to do some manual cleanup:
  412. - remove SSH keys from the destroyed cluster from your `~/.ssh/known_hosts` file
  413. - clean up any temporary cache files: `rm /tmp/$CLUSTER-*`
  414. ### Debugging
  415. You can enable debugging output from Terraform by setting
  416. `OS_DEBUG` to 1 and`TF_LOG` to`DEBUG` before running the Terraform command.
  417. ### Terraform output
  418. Terraform can output values that are useful for configure Neutron/Octavia LBaaS or Cinder persistent volume provisioning as part of your Kubernetes deployment:
  419. - `private_subnet_id`: the subnet where your instances are running is used for `openstack_lbaas_subnet_id`
  420. - `floating_network_id`: the network_id where the floating IP are provisioned is used for `openstack_lbaas_floating_network_id`
  421. ## Ansible
  422. ### Node access
  423. #### SSH
  424. Ensure your local ssh-agent is running and your ssh key has been added. This
  425. step is required by the terraform provisioner:
  426. ```ShellSession
  427. eval $(ssh-agent -s)
  428. ssh-add ~/.ssh/id_rsa
  429. ```
  430. If you have deployed and destroyed a previous iteration of your cluster, you will need to clear out any stale keys from your SSH "known hosts" file ( `~/.ssh/known_hosts`).
  431. #### Metadata variables
  432. The [python script](../terraform.py) that reads the
  433. generated`.tfstate` file to generate a dynamic inventory recognizes
  434. some variables within a "metadata" block, defined in a "resource"
  435. block (example):
  436. ```ini
  437. resource "openstack_compute_instance_v2" "example" {
  438. ...
  439. metadata {
  440. ssh_user = "ubuntu"
  441. prefer_ipv6 = true
  442. python_bin = "/usr/bin/python3"
  443. }
  444. ...
  445. }
  446. ```
  447. As the example shows, these let you define the SSH username for
  448. Ansible, a Python binary which is needed by Ansible if
  449. `/usr/bin/python` doesn't exist, and whether the IPv6 address of the
  450. instance should be preferred over IPv4.
  451. #### Bastion host
  452. Bastion access will be determined by:
  453. - Your choice on the amount of bastion hosts (set by `number_of_bastions` terraform variable).
  454. - The existence of nodes/masters with floating IPs (set by `number_of_k8s_masters`, `number_of_k8s_nodes`, `number_of_k8s_masters_no_etcd` terraform variables).
  455. If you have a bastion host, your ssh traffic will be directly routed through it. This is regardless of whether you have masters/nodes with a floating IP assigned.
  456. If you don't have a bastion host, but at least one of your masters/nodes have a floating IP, then ssh traffic will be tunneled by one of these machines.
  457. So, either a bastion host, or at least master/node with a floating IP are required.
  458. #### Test access
  459. Make sure you can connect to the hosts. Note that Flatcar Container Linux by Kinvolk will have a state `FAILED` due to Python not being present. This is okay, because Python will be installed during bootstrapping, so long as the hosts are not `UNREACHABLE`.
  460. ```ShellSession
  461. $ ansible -i inventory/$CLUSTER/hosts -m ping all
  462. example-k8s_node-1 | SUCCESS => {
  463. "changed": false,
  464. "ping": "pong"
  465. }
  466. example-etcd-1 | SUCCESS => {
  467. "changed": false,
  468. "ping": "pong"
  469. }
  470. example-k8s-master-1 | SUCCESS => {
  471. "changed": false,
  472. "ping": "pong"
  473. }
  474. ```
  475. If it fails try to connect manually via SSH. It could be something as simple as a stale host key.
  476. ### Configure cluster variables
  477. Edit `inventory/$CLUSTER/group_vars/all/all.yml`:
  478. - **bin_dir**:
  479. ```yml
  480. # Directory where the binaries will be installed
  481. # Default:
  482. # bin_dir: /usr/local/bin
  483. # For Flatcar Container Linux by Kinvolk:
  484. bin_dir: /opt/bin
  485. ```
  486. - and **cloud_provider**:
  487. ```yml
  488. cloud_provider: openstack
  489. ```
  490. Edit `inventory/$CLUSTER/group_vars/k8s_cluster/k8s_cluster.yml`:
  491. - Set variable **kube_network_plugin** to your desired networking plugin.
  492. - **flannel** works out-of-the-box
  493. - **calico** requires [configuring OpenStack Neutron ports](/docs/cloud_controllers/openstack.md) to allow service and pod subnets
  494. ```yml
  495. # Choose network plugin (calico, weave or flannel)
  496. # Can also be set to 'cloud', which lets the cloud provider setup appropriate routing
  497. kube_network_plugin: flannel
  498. ```
  499. - Set variable **resolvconf_mode**
  500. ```yml
  501. # Can be docker_dns, host_resolvconf or none
  502. # Default:
  503. # resolvconf_mode: docker_dns
  504. # For Flatcar Container Linux by Kinvolk:
  505. resolvconf_mode: host_resolvconf
  506. ```
  507. - Set max amount of attached cinder volume per host (default 256)
  508. ```yml
  509. node_volume_attach_limit: 26
  510. ```
  511. ### Deploy Kubernetes
  512. ```ShellSession
  513. ansible-playbook --become -i inventory/$CLUSTER/hosts cluster.yml
  514. ```
  515. This will take some time as there are many tasks to run.
  516. ## Kubernetes
  517. ### Set up kubectl
  518. 1. [Install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) on your workstation
  519. 2. Add a route to the internal IP of a master node (if needed):
  520. ```ShellSession
  521. sudo route add [master-internal-ip] gw [router-ip]
  522. ```
  523. or
  524. ```ShellSession
  525. sudo route add -net [internal-subnet]/24 gw [router-ip]
  526. ```
  527. 1. List Kubernetes certificates & keys:
  528. ```ShellSession
  529. ssh [os-user]@[master-ip] sudo ls /etc/kubernetes/ssl/
  530. ```
  531. 1. Get `admin`'s certificates and keys:
  532. ```ShellSession
  533. ssh [os-user]@[master-ip] sudo cat /etc/kubernetes/ssl/admin-kube-master-1-key.pem > admin-key.pem
  534. ssh [os-user]@[master-ip] sudo cat /etc/kubernetes/ssl/admin-kube-master-1.pem > admin.pem
  535. ssh [os-user]@[master-ip] sudo cat /etc/kubernetes/ssl/ca.pem > ca.pem
  536. ```
  537. 1. Configure kubectl:
  538. ```ShellSession
  539. $ kubectl config set-cluster default-cluster --server=https://[master-internal-ip]:6443 \
  540. --certificate-authority=ca.pem
  541. $ kubectl config set-credentials default-admin \
  542. --certificate-authority=ca.pem \
  543. --client-key=admin-key.pem \
  544. --client-certificate=admin.pem
  545. $ kubectl config set-context default-system --cluster=default-cluster --user=default-admin
  546. $ kubectl config use-context default-system
  547. ```
  548. 1. Check it:
  549. ```ShellSession
  550. kubectl version
  551. ```
  552. ## GlusterFS
  553. GlusterFS is not deployed by the standard `cluster.yml` playbook, see the
  554. [GlusterFS playbook documentation](../../network-storage/glusterfs/README.md)
  555. for instructions.
  556. Basically you will install Gluster as
  557. ```ShellSession
  558. ansible-playbook --become -i inventory/$CLUSTER/hosts ./contrib/network-storage/glusterfs/glusterfs.yml
  559. ```
  560. ## What's next
  561. Try out your new Kubernetes cluster with the [Hello Kubernetes service](https://kubernetes.io/docs/tasks/access-application-cluster/service-access-application-cluster/).
  562. ## Appendix
  563. ### Migration from `number_of_k8s_nodes*` to `k8s_nodes`
  564. If you currently have a cluster defined using the `number_of_k8s_nodes*` variables and wish
  565. to migrate to the `k8s_nodes` style you can do it like so:
  566. ```ShellSession
  567. $ terraform state list
  568. module.compute.data.openstack_images_image_v2.gfs_image
  569. module.compute.data.openstack_images_image_v2.vm_image
  570. module.compute.openstack_compute_floatingip_associate_v2.k8s_master[0]
  571. module.compute.openstack_compute_floatingip_associate_v2.k8s_node[0]
  572. module.compute.openstack_compute_floatingip_associate_v2.k8s_node[1]
  573. module.compute.openstack_compute_floatingip_associate_v2.k8s_node[2]
  574. module.compute.openstack_compute_instance_v2.k8s_master[0]
  575. module.compute.openstack_compute_instance_v2.k8s_node[0]
  576. module.compute.openstack_compute_instance_v2.k8s_node[1]
  577. module.compute.openstack_compute_instance_v2.k8s_node[2]
  578. module.compute.openstack_compute_keypair_v2.k8s
  579. module.compute.openstack_compute_servergroup_v2.k8s_etcd[0]
  580. module.compute.openstack_compute_servergroup_v2.k8s_master[0]
  581. module.compute.openstack_compute_servergroup_v2.k8s_node[0]
  582. module.compute.openstack_networking_secgroup_rule_v2.bastion[0]
  583. module.compute.openstack_networking_secgroup_rule_v2.egress[0]
  584. module.compute.openstack_networking_secgroup_rule_v2.k8s
  585. module.compute.openstack_networking_secgroup_rule_v2.k8s_allowed_remote_ips[0]
  586. module.compute.openstack_networking_secgroup_rule_v2.k8s_allowed_remote_ips[1]
  587. module.compute.openstack_networking_secgroup_rule_v2.k8s_allowed_remote_ips[2]
  588. module.compute.openstack_networking_secgroup_rule_v2.k8s_master[0]
  589. module.compute.openstack_networking_secgroup_rule_v2.worker[0]
  590. module.compute.openstack_networking_secgroup_rule_v2.worker[1]
  591. module.compute.openstack_networking_secgroup_rule_v2.worker[2]
  592. module.compute.openstack_networking_secgroup_rule_v2.worker[3]
  593. module.compute.openstack_networking_secgroup_rule_v2.worker[4]
  594. module.compute.openstack_networking_secgroup_v2.bastion[0]
  595. module.compute.openstack_networking_secgroup_v2.k8s
  596. module.compute.openstack_networking_secgroup_v2.k8s_master
  597. module.compute.openstack_networking_secgroup_v2.worker
  598. module.ips.null_resource.dummy_dependency
  599. module.ips.openstack_networking_floatingip_v2.k8s_master[0]
  600. module.ips.openstack_networking_floatingip_v2.k8s_node[0]
  601. module.ips.openstack_networking_floatingip_v2.k8s_node[1]
  602. module.ips.openstack_networking_floatingip_v2.k8s_node[2]
  603. module.network.openstack_networking_network_v2.k8s[0]
  604. module.network.openstack_networking_router_interface_v2.k8s[0]
  605. module.network.openstack_networking_router_v2.k8s[0]
  606. module.network.openstack_networking_subnet_v2.k8s[0]
  607. $ terraform state mv 'module.compute.openstack_compute_floatingip_associate_v2.k8s_node[0]' 'module.compute.openstack_compute_floatingip_associate_v2.k8s_nodes["1"]'
  608. Move "module.compute.openstack_compute_floatingip_associate_v2.k8s_node[0]" to "module.compute.openstack_compute_floatingip_associate_v2.k8s_nodes[\"1\"]"
  609. Successfully moved 1 object(s).
  610. $ terraform state mv 'module.compute.openstack_compute_floatingip_associate_v2.k8s_node[1]' 'module.compute.openstack_compute_floatingip_associate_v2.k8s_nodes["2"]'
  611. Move "module.compute.openstack_compute_floatingip_associate_v2.k8s_node[1]" to "module.compute.openstack_compute_floatingip_associate_v2.k8s_nodes[\"2\"]"
  612. Successfully moved 1 object(s).
  613. $ terraform state mv 'module.compute.openstack_compute_floatingip_associate_v2.k8s_node[2]' 'module.compute.openstack_compute_floatingip_associate_v2.k8s_nodes["3"]'
  614. Move "module.compute.openstack_compute_floatingip_associate_v2.k8s_node[2]" to "module.compute.openstack_compute_floatingip_associate_v2.k8s_nodes[\"3\"]"
  615. Successfully moved 1 object(s).
  616. $ terraform state mv 'module.compute.openstack_compute_instance_v2.k8s_node[0]' 'module.compute.openstack_compute_instance_v2.k8s_node["1"]'
  617. Move "module.compute.openstack_compute_instance_v2.k8s_node[0]" to "module.compute.openstack_compute_instance_v2.k8s_node[\"1\"]"
  618. Successfully moved 1 object(s).
  619. $ terraform state mv 'module.compute.openstack_compute_instance_v2.k8s_node[1]' 'module.compute.openstack_compute_instance_v2.k8s_node["2"]'
  620. Move "module.compute.openstack_compute_instance_v2.k8s_node[1]" to "module.compute.openstack_compute_instance_v2.k8s_node[\"2\"]"
  621. Successfully moved 1 object(s).
  622. $ terraform state mv 'module.compute.openstack_compute_instance_v2.k8s_node[2]' 'module.compute.openstack_compute_instance_v2.k8s_node["3"]'
  623. Move "module.compute.openstack_compute_instance_v2.k8s_node[2]" to "module.compute.openstack_compute_instance_v2.k8s_node[\"3\"]"
  624. Successfully moved 1 object(s).
  625. $ terraform state mv 'module.ips.openstack_networking_floatingip_v2.k8s_node[0]' 'module.ips.openstack_networking_floatingip_v2.k8s_node["1"]'
  626. Move "module.ips.openstack_networking_floatingip_v2.k8s_node[0]" to "module.ips.openstack_networking_floatingip_v2.k8s_node[\"1\"]"
  627. Successfully moved 1 object(s).
  628. $ terraform state mv 'module.ips.openstack_networking_floatingip_v2.k8s_node[1]' 'module.ips.openstack_networking_floatingip_v2.k8s_node["2"]'
  629. Move "module.ips.openstack_networking_floatingip_v2.k8s_node[1]" to "module.ips.openstack_networking_floatingip_v2.k8s_node[\"2\"]"
  630. Successfully moved 1 object(s).
  631. $ terraform state mv 'module.ips.openstack_networking_floatingip_v2.k8s_node[2]' 'module.ips.openstack_networking_floatingip_v2.k8s_node["3"]'
  632. Move "module.ips.openstack_networking_floatingip_v2.k8s_node[2]" to "module.ips.openstack_networking_floatingip_v2.k8s_node[\"3\"]"
  633. Successfully moved 1 object(s).
  634. ```
  635. Of course for nodes without floating ips those steps can be omitted.