本教程介绍了如何通过Terraform在VPC环境下部署一个阿里云容器服务Kubernetes集群,并在该集群之上,部署一个WordPress样例应用。

前提条件

在使用本教程之前,请确保完成以下准备工作:

步骤一:下载容器服务Kubernetes的Terraform模板

您可以从GitHub上下载创建Kubernetes集群的Terraform模板(模板下载地址),模板中包含以下文件:

  • main.tf

    Terraform主文件。定义了将要部署的资源。本模版加入了条件判断,可实现对已有网络资源的引用和多个Kubernetes集群的同时创建。该文件定义了以下资源:

    • 可用地域
      定义了资源将要被创建在哪个地域里。
      provider "alicloud" {
        access_key = "${var.alicloud_access_key}"
        secret_key = "${var.alicloud_secret_key}"
        region = "${var.region}"
      }
      
      data "alicloud_zones" "default" {
        available_instance_type = data.alicloud_instance_types.default.instance_types[0].id
      }
    • 实例规格
      data "alicloud_instance_types" "default" {
        cpu_core_count = var.cpu_core_count
        memory_size    = var.memory_size
      }
    • 专有网络
      指定vpc_id可使用已有VPC。
      resource "alicloud_vpc" "vpc" {
        count      = var.vpc_id == "" ? 1 : 0
        cidr_block = var.vpc_cidr
        name       = var.vpc_name == "" ? var.example_name : var.vpc_name
      }                                 
    • 交换机
      指定vswitch_ids可使用已有交换机。
      resource "alicloud_vswitch" "vswitches" {
        count             = length(var.vswitch_ids) > 0 ? 0 : length(var.vswitch_cidrs)
        vpc_id            = var.vpc_id == "" ? join("", alicloud_vpc.vpc.*.id) : var.vpc_id
        cidr_block        = element(var.vswitch_cidrs, count.index)
        availability_zone = data.alicloud_zones.default.zones[count.index % length(data.alicloud_zones.default.zones)]["id"]
        name = var.vswitch_name_prefix == "" ? format(
          "%s-%s",
          var.example_name,
          format(var.number_format, count.index + 1),
          ) : format(
          "%s-%s",
          var.vswitch_name_prefix,
          format(var.number_format, count.index + 1),
        )
      }                                
    • NAT网关
      指定new_nat_gateway来决定是否要为模板中定义的 VPC 自动创建NAT网关,以保证Kubernetes集群成功创建。
      resource "alicloud_nat_gateway" "default" {
        count  = var.new_nat_gateway == "true" ? 1 : 0
        vpc_id = var.vpc_id == "" ? join("", alicloud_vpc.vpc.*.id) : var.vpc_id
        name   = var.example_name
      }                                
    • 弹性网卡
      resource "alicloud_eip" "default" {
        count     = var.new_nat_gateway == "true" ? 1 : 0
        bandwidth = 10
      }
    • 绑定弹性网卡
      resource "alicloud_eip_association" "default" {
        count         = var.new_nat_gateway == "true" ? 1 : 0
        allocation_id = alicloud_eip.default[0].id
        instance_id   = alicloud_nat_gateway.default[0].id
      }
    • 添加SNAT条目
      在模板中定义的NAT网关下自动添加SNAT条目来保证Kubernetes集群成功创建。
      resource "alicloud_snat_entry" "default" {
        count         = var.new_nat_gateway == "false" ? 0 : length(var.vswitch_ids) > 0 ? length(var.vswitch_ids) : length(var.vswitch_cidrs)
        snat_table_id = alicloud_nat_gateway.default[0].snat_table_ids
        source_vswitch_id = length(var.vswitch_ids) > 0 ? split(",", join(",", var.vswitch_ids))[count.index % length(split(",", join(",", var.vswitch_ids)))] : length(var.vswitch_cidrs) < 1 ? "" : split(",", join(",", alicloud_vswitch.vswitches.*.id))[count.index % length(split(",", join(",", alicloud_vswitch.vswitches.*.id)))]
        snat_ip = alicloud_eip.default[0].ip_address
      }
    • 容器服务Kubernetes集群
      改变k8s_number的值可同时创建多个Kubernetes集群。
      resource "alicloud_cs_kubernetes" "k8s" {
        count = var.k8s_number
        name = var.k8s_name_prefix == "" ? format(
          "%s-%s",
          var.example_name,
          format(var.number_format, count.index + 1),
          ) : format(
          "%s-%s",
          var.k8s_name_prefix,
          format(var.number_format, count.index + 1),
        )
        vswitch_ids = [length(var.vswitch_ids) > 0 ? split(",", join(",", var.vswitch_ids))[count.index % length(split(",", join(",", var.vswitch_ids)))] : length(var.vswitch_cidrs) < 1 ? "" : split(",", join(",", alicloud_vswitch.vswitches.*.id))[count.index % length(split(",", join(",", alicloud_vswitch.vswitches.*.id)))]]
        new_nat_gateway       = false
        master_instance_types = [var.master_instance_type == "" ? data.alicloud_instance_types.default.instance_types[0].id : var.master_instance_type]
        worker_instance_types = [var.worker_instance_type == "" ? data.alicloud_instance_types.default.instance_types[0].id : var.worker_instance_type]
        worker_numbers        = [var.k8s_worker_number]
        master_disk_category  = var.master_disk_category
        worker_disk_category  = var.worker_disk_category
        master_disk_size      = var.master_disk_size
        worker_disk_size      = var.master_disk_size
        password              = var.ecs_password
        pod_cidr              = var.k8s_pod_cidr
        service_cidr          = var.k8s_service_cidr
        enable_ssh            = true
        install_cloud_monitor = true
        depends_on = [alicloud_snat_entry.default]
      }                                
      说明 指定kube_config = "~/.kube/config"可在Kubernetes集群创建完成后将Kube Config内容自动下载并存放在文件~/.kube/cofig中。
  • outputs.tf
    该文件定义了输出参数。作为执行的一部分而创建的资源会生成这些输出参数。和ROS模板指定的输出参数类似。例如,该模板将部署一个Kubernetes集群。以下输出参数将提供集群ID和其他资源参数。
    // Output VPC
    output "vpc_id" {
      description = "The ID of the VPC."
      value       = alicloud_cs_kubernetes.k8s[0].vpc_id
    }
    
    output "vswitch_ids" {
      description = "List ID of the VSwitches."
      value       = [alicloud_cs_kubernetes.k8s.*.vswitch_ids]
    }
    
    output "nat_gateway_id" {
      value = alicloud_cs_kubernetes.k8s[0].nat_gateway_id
    }
    
    // Output kubernetes resource
    output "cluster_id" {
      description = "ID of the kunernetes cluster."
      value       = [alicloud_cs_kubernetes.k8s.*.id]
    }
    
    output "security_group_id" {
      description = "ID of the Security Group used to deploy kubernetes cluster."
      value       = alicloud_cs_kubernetes.k8s[0].security_group_id
    }
    
    output "worker_nodes" {
      description = "List worker nodes of cluster."
      value       = [alicloud_cs_kubernetes.k8s.*.worker_nodes]
    }
    
    output "master_nodes" {
      description = "List master nodes of cluster."
      value       = [alicloud_cs_kubernetes.k8s.*.master_nodes]
    }
  • variables.tf
    该文件包含可传递到main.tf的变量,可帮助您自定义环境。
    # common variables
    variable "availability_zone" {
      description = "The available zone to launch ecs instance and other resources."
      default     = ""
    }
    
    variable "number_format" {
      description = "The number format used to output."
      default     = "%02d"
    }
    
    variable "example_name" {
      default = "tf-example-kubernetes"
    }
    
    # Instance typs variables
    variable "cpu_core_count" {
      description = "CPU core count is used to fetch instance types."
      default     = 2
    }
    
    variable "memory_size" {
      description = "Memory size used to fetch instance types."
      default     = 4
    }
    
    # VPC variables
    variable "vpc_name" {
      description = "The vpc name used to create a new vpc when 'vpc_id' is not specified. Default to variable `example_name`"
      default     = ""
    }
    
    variable "vpc_id" {
      description = "A existing vpc id used to create several vswitches and other resources."
      default     = ""
    }
    
    variable "vpc_cidr" {
      description = "The cidr block used to launch a new vpc when 'vpc_id' is not specified."
      default     = "10.1.0.0/21"
    }
    
    # VSwitch variables
    variable "vswitch_name_prefix" {
      description = "The vswitch name prefix used to create several new vswitches. Default to variable `example_name`"
      default     = ""
    }
    
    variable "vswitch_ids" {
      description = "List of existing vswitch id."
      type        = list(string)
      default     = []
    }
    
    variable "vswitch_cidrs" {
      description = "List of cidr blocks used to create several new vswitches when 'vswitch_ids' is not specified."
      type        = list(string)
      default     = ["10.1.2.0/24"]
    }
    
    variable "new_nat_gateway" {
      description = "Whether to create a new nat gateway. In this template, a new nat gateway will create a nat gateway, eip and server snat entries."
      default     = "true"
    }
    
    # Cluster nodes variables
    variable "master_instance_type" {
      description = "The ecs instance type used to launch master nodes. Default from instance typs datasource."
      default     = ""
    }
    
    variable "worker_instance_type" {
      description = "The ecs instance type used to launch worker nodes. Default from instance typs datasource."
      default     = ""
    }
    
    variable "master_disk_category" {
      description = "The system disk category used to launch one or more master nodes."
      default     = "cloud_efficiency"
    }
    
    variable "worker_disk_category" {
      description = "The system disk category used to launch one or more worker nodes."
      default     = "cloud_efficiency"
    }
    
    variable "master_disk_size" {
      description = "The system disk size used to launch one or more master nodes."
      default     = "40"
    }
    
    variable "worker_disk_size" {
      description = "The system disk size used to launch one or more worker nodes."
      default     = "40"
    }
    
    variable "ecs_password" {
      description = "The password of instance."
      default     = "Abc12345"
    }
    
    variable "k8s_number" {
      description = "The number of kubernetes cluster."
      default     = 1
    }
    
    variable "k8s_worker_number" {
      description = "The number of worker nodes in each kubernetes cluster."
      default     = 3
    }
    
    variable "k8s_name_prefix" {
      description = "The name prefix used to create several kubernetes clusters. Default to variable `example_name`"
      default     = ""
    }
    
    variable "k8s_pod_cidr" {
      description = "The kubernetes pod cidr block. It cannot be equals to vpc's or vswitch's and cannot be in them."
      default     = "172.20.0.0/16"
    }
    
    variable "k8s_service_cidr" {
      description = "The kubernetes service cidr block. It cannot be equals to vpc's or vswitch's or pod's and cannot be in them."
      default     = "172.21.0.0/20"
    }

步骤二:执行Kubernetes Terraform脚本

  1. 在存放以上文件的路径,运行terraform init命令,初始化工作空间。
    $ terraform init
    
    Initializing the backend...
    
    Initializing provider plugins...
    - Checking for available provider plugins...
    - Downloading plugin for provider "alicloud" (hashicorp/alicloud) 1.62.0...
    
    The following providers do not have any version constraints in configuration,
    so the latest version was installed.
    
    To prevent automatic upgrades to new major versions that may contain breaking
    changes, it is recommended to add version = "..." constraints to the
    corresponding provider blocks in configuration, with the constraint strings
    suggested below.
    
    * provider.alicloud: version = "~> 1.62"
    
    Terraform has been successfully initialized!
    						
  2. 运行terraform apply 命令,开始创建Kubernetes集群。
    $ terraform apply
    
    data.alicloud_instance_types.default: Refreshing state...
    data.alicloud_zones.default: Refreshing state...
    
    An execution plan has been generated and is shown below.
    Resource actions are indicated with the following symbols:
      + create
    
    Terraform will perform the following actions:
    ...
    
    Plan: 7 to add, 0 to change, 0 to destroy.
    
    Do you want to perform these actions?
      Terraform will perform the actions described above.
      Only 'yes' will be accepted to approve.
    
      Enter a value: yes
    
    alicloud_vpc.vpc: Creating...
    ...
    
    Apply complete! Resources: 7 added, 0 changed, 0 destroyed.
    
    Outputs:    
    
    cluster_id = [
        c0f2e04c77e234******
    ]
    ......
    vswitch_ids = [
        vsw-bp1c3hfcd6l80izqc3tbx
    ]
    						

    terraform apply命令执行完毕后,输出集群ID和其他参数。除此之外,将Kubernetes的Kube Config文件存放在了目录 ~/.kube 下。

    您现在可以在容器服务控制台查看通过terraform创建的Kubernetes集群,查看集群、节点、日志和容器等信息。

步骤三:下载Wordpress的Terraform模板

在创建好Kubernetes并完成了Kube Config的下载后,接下来就可以在Kubernetes上部署Wordpress。 您可以从GitHub上下载创建Wordpress的 Terraform模板(模板下载地址,模板中定义了创建Wordpress的相关资源和配置,帮助您完成在Kubernetes集群的快速搭建Wordpress。更多 Terraform Kubernetes的操作可参考Terraform官网的 Kubernetes 文档介绍

模板中包含以下文件:
  • localvolumes.tf
    定义存储Mysql持久化数据的Persistent Volume。
    resource "kubernetes_persistent_volume" "mysql" {
      metadata {
        name = "local-pv-mysql"
        labels {
          type = "local"
        }
      }
      spec {
        capacity {
          storage = "20Gi"
        }
        access_modes = ["ReadWriteOnce"]
        persistent_volume_source {
          host_path {
            path = "/tmp/data/pv-mysql"
          }
        }
      }
    }
  • mysql.tf
    创建MySQL密码凭证Secret,并部署MySQL。
    • secret
      resource "kubernetes_secret" "mysql" {
        metadata {
          name = "mysql-pass"
        }
      
        data {
          password = "${var.mysql_password}"
        }
      }
    • Deployment
      resource "kubernetes_service" "mysql" {
        metadata {
          name = "wordpress-mysql"
          labels {
            app = "wordpress"
          }
        }
        spec {
          port {
            port = 3306
          }
          selector {
            app = "wordpress"
            tier = "${kubernetes_replication_controller.mysql.spec.0.selector.tier}"
          }
          cluster_ip = "None"
        }
      }
      
      resource "kubernetes_replication_controller" "mysql" {
        metadata {
          name = "wordpress-mysql"
          labels {
            app = "wordpress"
          }
        }
        spec {
          selector {
            app = "wordpress"
            tier = "mysql"
          }
          template {
            container {
              image = "mysql:${var.mysql_version}"
              name  = "mysql"
      
              env {
                name = "MYSQL_ROOT_PASSWORD"
                value_from {
                  secret_key_ref {
                    name = "${kubernetes_secret.mysql.metadata.0.name}"
                    key = "password"
                  }
                }
              }
      
              port {
                container_port = 3306
                name = "mysql"
              }
      
              volume_mount {
                name = "mysql-persistent-storage"
                mount_path = "/var/lib/mysql"
              }
            }
      
            volume {
              name = "mysql-persistent-storage"
              persistent_volume_claim {
                claim_name = "${kubernetes_persistent_volume_claim.mysql.metadata.0.name}"
              }
            }
          }
        }
      }
      									
  • wordpress.tf
    部署Wordpress。
    resource "kubernetes_service" "wordpress" {
      metadata {
        name = "wordpress"
        labels {
          app = "wordpress"
        }
      }
      spec {
        port {
          port = 80
        }
        selector {
          app = "wordpress"
          tier = "${kubernetes_replication_controller.wordpress.spec.0.selector.tier}"
        }
        type = "LoadBalancer"
      }
    }
    
    resource "kubernetes_replication_controller" "wordpress" {
      metadata {
        name = "wordpress"
        labels {
          app = "wordpress"
        }
      }
      spec {
        selector {
          app = "wordpress"
          tier = "frontend"
        }
        template {
          container {
            image = "wordpress:${var.wordpress_version}-apache"
            name  = "wordpress"
    
            env {
              name = "WORDPRESS_DB_HOST"
              value = "wordpress-mysql"
            }
            env {
              name = "WORDPRESS_DB_PASSWORD"
              value_from {
                secret_key_ref {
                  name = "${kubernetes_secret.mysql.metadata.0.name}"
                  key = "password"
                }
              }
            }
    
            port {
              container_port = 80
              name = "wordpress"
            }
    
            volume_mount {
              name = "wordpress-persistent-storage"
              mount_path = "/var/www/html"
            }
          }
    
          volume {
            name = "wordpress-persistent-storage"
            persistent_volume_claim {
              claim_name = "${kubernetes_persistent_volume_claim.wordpress.metadata.0.name}"
            }
          }
        }
      }
    }
  • outputs.tf
    该文件定义了输出参数。输出Loadbalancer Public IP,借助该IP地址可直接访问部署好的Wordpress应用。
    output "slb_ip" {
      value = "${kubernetes_service.wordpress.load_balancer_ingress.0.ip}"
    }
  • variables.tf
    该文件包含了部署MySQL和WordPress所依赖的参数。
    variable "wordpress_version" {
      description = "The version of wordpress. Default to 4.7.3."
      default = "4.7.3"
    }
    variable "mysql_password" {
      description = "Please input mysql password."
    }
    variable "mysql_version" {
      description = "The version of mysql which wordpress used. Default to 5.6."
      default = "5.6"
    }

步骤四:执行Wordpress Terraform脚本

首先定位到您存放以上文件的目录,如 /root/terraform/kuberneters-wordpress。运行terraform apply命令,开始在创建好的Kubernetes集群上部署MySQL和WordPress应用。值得注意的是,由于变量mysql_password在变量文件中没有定义默认值,因此在执行命令时需要指定该参数值。

$ terraform apply -var 'mysql_password=Abc1234'

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:
...

Plan: 9 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

kubernetes_secret.mysql: Creating...
  data.%:                      "" => "1"
  data.password:               "<sensitive>" => "<sensitive>"
  metadata.#:                  "" => "1"
  metadata.0.generation:       "" => "<computed>"
  metadata.0.name:             "" => "mysql-pass"

......

Apply complete! Resources: 9 added, 0 changed, 0 destroyed.

Outputs:

slb_ip = 47.99.xx.xx

步骤五:访问WordPress

根据负载均衡Public IP,在浏览器中输入IP地址即可实现对部署好的Wordpress直接访问:

  1. 进入WordPress欢迎页面,选择语言,然后继续配置。wp_step1
  2. 输入站点名称以及管理员的用户名和密码。选择安装WordPress。wp_step2
  3. WordPress安装完成后,单击 登录,输入管理员的用户名和密码,进入WordPress应用。wp_step3