Terraform Apps and Infra 6 - Hands-on with Providers

Providers

AWS: Set up apache server

  • Code

    • main.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      # Create and bootstrap webserver
      # create EC2 server, parameters come from setup.tf file
      resource "aws_instance" "webserver" {
      ami = data.aws_ssm_parameter.webserver-ami.value
      instance_type = "t3.micro"
      key_name = aws_key_pair.webserver-key.key_name
      associate_public_ip_address = true
      vpc_security_group_ids = [aws_security_group.sg.id]
      subnet_id = aws_subnet.subnet.id
      # if its remote, execute this code using the parameters embedded on connection
      provisioner "remote-exec" {
      inline = [
      "sudo yum -y install httpd && sudo systemctl start httpd",
      "echo '<h1><center>My Test Website With Help From Terraform Provisioner</center></h1>' > index.html",
      "sudo mv index.html /var/www/html/"
      ]
      connection {
      type = "ssh"
      user = "ec2-user"
      private_key = file("~/.ssh/id_rsa")
      host = self.public_ip
      }
      }
      tags = {
      Name = "webserver"
      }
      }
    • setup.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      provider "aws" {
      region = "us-east-1"
      }

      # Create key-pair for logging into EC2 in us-east-1
      resource "aws_key_pair" "webserver-key" {
      key_name = "webserver-key"
      public_key = file("~/.ssh/id_rsa.pub")
      }


      # Get Linux AMI ID using SSM Parameter endpoint in us-east-1
      data "aws_ssm_parameter" "webserver-ami" {
      name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
      }

      # Create VPC in us-east-1
      resource "aws_vpc" "vpc" {
      cidr_block = "10.0.0.0/16"
      enable_dns_support = true
      enable_dns_hostnames = true
      tags = {
      Name = "terraform-vpc"
      }

      }

      # Create IGW in us-east-1
      resource "aws_internet_gateway" "igw" {
      vpc_id = aws_vpc.vpc.id
      }

      # Get main route table to modify
      data "aws_route_table" "main_route_table" {
      filter {
      name = "association.main"
      values = ["true"]
      }
      filter {
      name = "vpc-id"
      values = [aws_vpc.vpc.id]
      }
      }
      # Create route table in us-east-1
      resource "aws_default_route_table" "internet_route" {
      default_route_table_id = data.aws_route_table.main_route_table.id
      route {
      cidr_block = "0.0.0.0/0"
      gateway_id = aws_internet_gateway.igw.id
      }
      tags = {
      Name = "Terraform-RouteTable"
      }
      }

      #Get all available AZ's in VPC for master region
      data "aws_availability_zones" "azs" {
      state = "available"
      }

      #Create subnet # 1 in us-east-1
      resource "aws_subnet" "subnet" {
      availability_zone = element(data.aws_availability_zones.azs.names, 0)
      vpc_id = aws_vpc.vpc.id
      cidr_block = "10.0.1.0/24"
      }


      #Create SG for allowing TCP/80 & TCP/22
      resource "aws_security_group" "sg" {
      name = "sg"
      description = "Allow TCP/80 & TCP/22"
      vpc_id = aws_vpc.vpc.id
      ingress {
      description = "Allow SSH traffic"
      from_port = 22
      to_port = 22
      protocol = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
      }
      ingress {
      description = "allow traffic from TCP/80"
      from_port = 80
      to_port = 80
      protocol = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
      }
      egress {
      from_port = 0
      to_port = 0
      protocol = "-1"
      cidr_blocks = ["0.0.0.0/0"]
      }
      }

      output "Webserver-Public-IP" {
      value = aws_instance.webserver.public_ip
      }
  • On CLI

    1
    2
    3
    4
    5
    6
    terraform init
    terraform validate
    terraform plan
    terraform apply
    # terraform provisioner tries to connect to the EC2 instance
    # then runs bootstraped code

Azure: Deploy WebApp

  • Code

    • Basic main.tf for Azure

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      # Configure the Azure provider
      terraform {
      required_providers {
      azurerm = {
      source = "hashicorp/azurerm"
      version = ">= 2.26"
      }
      }

      required_version = ">= 0.14.9"
      }

      provider "azurerm" {
      features {}
      skip_provider_registration = true
      }

      # Create a virtual network
      resource "azurerm_virtual_network" "vnet" {
      name = "BatmanInc"
      address_space = ["10.0.0.0/16"]
      location = "Central US"
      resource_group_name = "<ADD YOUR RESOURCE GROUP NAME>"
      }
    • maint.tf fpor webapp on Azure

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      provider "azurerm" {
      version = 1.38
      }

      resource "azurerm_app_service_plan" "svcplan" {
      name = "Enter App Service Plan name"
      location = "eastus"
      resource_group_name = "Enter Resource Group Name"

      sku {
      tier = "Standard"
      size = "S1"
      }
      }

      resource "azurerm_app_service" "appsvc" {
      name = "Enter Web App Service Name"
      location = "eastus"
      resource_group_name = "Enter Resource Group Name"
      app_service_plan_id = azurerm_app_service_plan.svcplan.id


      site_config {
      dotnet_framework_version = "v4.0"
      scm_type = "LocalGit"
      }
      }
  • CLI

    1
    2
    3
    terraform init
    terraform validate
    terraform plan

Kubernetes deployment

  • Code

    • kubernetes.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      terraform {
      required_providers {
      kubernetes = {
      source = "hashicorp/kubernetes"
      }
      }
      }

      variable "host" {
      type = string
      }

      variable "client_certificate" {
      type = string
      }

      variable "client_key" {
      type = string
      }

      variable "cluster_ca_certificate" {
      type = string
      }

      provider "kubernetes" {
      host = var.host

      client_certificate = base64decode(var.client_certificate)
      client_key = base64decode(var.client_key)
      cluster_ca_certificate = base64decode(var.cluster_ca_certificate)
      }
    • terraform.tfvars
      1
      2
      3
      4
      host                   = "DUMMY VALUE"
      client_certificate = "DUMMY VALUE"
      client_key = "DUMMY VALUE"
      cluster_ca_certificate = "DUMMY VALUE"
    • .kind-config
      1
      2
      3
      4
      5
      6
      7
      8
      kind: Cluster
      apiVersion: kind.x-k8s.io/v1alpha4
      nodes:
      - role: control-plane
      extraPortMappings:
      - containerPort: 30201
      hostPort: 30201
      listenAddress: "0.0.0.0"
  • CLI

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # PREPARE KUBERNETES
    # create Kubernetes cluster, using kind-cli
    kind create cluster --name lab-terraform-kubernetes --config kind-config.yaml
    kubectl cluster-info --context kind-lab-terraform-kubernetes
    # verify
    kind get clusters
    # get the server data
    kubectl config view --minify --flatten --context=kind-lab-terraform-kubernetes
    # put the server address, client-key-data into terraform.tfvars

    # DEPLOY IT
    terraform init
    terraform validate
    terraform plan
    # validate "long-live-the-bat" exists
    kubectl get deployments

AWS EKS deployment

  • Code

    • kubernetes.tf
      1
      2
      3
      4
      5
      provider "kubernetes" {
      host = data.aws_eks_cluster.cluster.endpoint
      token = data.aws_eks_cluster_auth.cluster.token
      cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
      }
    • eks-cluster.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      module "eks" {
      source = "terraform-aws-modules/eks/aws"
      version = "17.24.0"
      cluster_name = local.cluster_name
      cluster_version = "1.20"
      subnets = module.vpc.private_subnets

      tags = {
      Environment = "training"
      GithubRepo = "terraform-aws-eks"
      GithubOrg = "terraform-aws-modules"
      }

      vpc_id = module.vpc.vpc_id

      workers_group_defaults = {
      root_volume_type = "gp2"
      }

      worker_groups = [
      {
      name = "worker-group-1"
      instance_type = "t2.small"
      additional_userdata = "echo foo bar"
      asg_desired_capacity = 2
      additional_security_group_ids = [aws_security_group.worker_group_mgmt_one.id]
      },
      {
      name = "worker-group-2"
      instance_type = "t2.medium"
      additional_userdata = "echo foo bar"
      additional_security_group_ids = [aws_security_group.worker_group_mgmt_two.id]
      asg_desired_capacity = 1
      },
      ]
      }

      data "aws_eks_cluster" "cluster" {
      name = module.eks.cluster_id
      }

      data "aws_eks_cluster_auth" "cluster" {
      name = module.eks.cluster_id
      }
    • security-groups.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      resource "aws_security_group" "worker_group_mgmt_one" {
      name_prefix = "worker_group_mgmt_one"
      vpc_id = module.vpc.vpc_id

      ingress {
      from_port = 22
      to_port = 22
      protocol = "tcp"

      cidr_blocks = [
      "10.0.0.0/8",
      ]
      }
      }

      resource "aws_security_group" "worker_group_mgmt_two" {
      name_prefix = "worker_group_mgmt_two"
      vpc_id = module.vpc.vpc_id

      ingress {
      from_port = 22
      to_port = 22
      protocol = "tcp"

      cidr_blocks = [
      "192.168.0.0/16",
      ]
      }
      }

      resource "aws_security_group" "all_worker_mgmt" {
      name_prefix = "all_worker_management"
      vpc_id = module.vpc.vpc_id

      ingress {
      from_port = 22
      to_port = 22
      protocol = "tcp"

      cidr_blocks = [
      "10.0.0.0/8",
      "172.16.0.0/12",
      "192.168.0.0/16",
      ]
      }
      }
    • vpc.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      variable "region" {
      default = "us-east-1"
      description = "AWS region"
      }

      provider "aws" {
      region = "us-east-1"
      }

      data "aws_availability_zones" "available" {}

      locals {
      cluster_name = "education-eks-${random_string.suffix.result}"
      }

      resource "random_string" "suffix" {
      length = 8
      special = false
      }

      module "vpc" {
      source = "terraform-aws-modules/vpc/aws"
      version = "2.66.0"

      name = "education-vpc"
      cidr = "10.0.0.0/16"
      azs = data.aws_availability_zones.available.names
      private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
      public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"]
      enable_nat_gateway = true
      single_nat_gateway = true
      enable_dns_hostnames = true

      tags = {
      "kubernetes.io/cluster/${local.cluster_name}" = "shared"
      }

      public_subnet_tags = {
      "kubernetes.io/cluster/${local.cluster_name}" = "shared"
      "kubernetes.io/role/elb" = "1"
      }

      private_subnet_tags = {
      "kubernetes.io/cluster/${local.cluster_name}" = "shared"
      "kubernetes.io/role/internal-elb" = "1"
      }
      }
    • versions.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      terraform {
      required_providers {
      aws = {
      source = "hashicorp/aws"
      version = ">= 3.20.0"
      }

      random = {
      source = "hashicorp/random"
      version = "3.0.0"
      }

      local = {
      source = "hashicorp/local"
      version = "2.0.0"
      }

      null = {
      source = "hashicorp/null"
      version = "3.0.0"
      }

      template = {
      source = "hashicorp/template"
      version = "2.2.0"
      }

      kubernetes = {
      source = "hashicorp/kubernetes"
      version = ">= 2.0.1"
      }
      }

      required_version = "> 0.14"
      }
    • outouts.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      output "cluster_id" {
      description = "EKS cluster ID."
      value = module.eks.cluster_id
      }

      output "cluster_endpoint" {
      description = "Endpoint for EKS control plane."
      value = module.eks.cluster_endpoint
      }

      output "cluster_security_group_id" {
      description = "Security group ids attached to the cluster control plane."
      value = module.eks.cluster_security_group_id
      }

      output "kubectl_config" {
      description = "kubectl config as generated by the module."
      value = module.eks.kubeconfig
      }

      output "config_map_aws_auth" {
      description = "A kubernetes configuration to authenticate to this EKS cluster."
      value = module.eks.config_map_aws_auth
      }

      output "region" {
      description = "AWS region"
      value = var.region
      }

      output "cluster_name" {
      description = "Kubernetes Cluster Name"
      value = local.cluster_name
      }
    • lab_kubernetes_resources.tf(for nginx, added to dir when required by CLI steps)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      resource "kubernetes_deployment" "nginx" {
      metadata {
      name = "long-live-the-bat"
      labels = {
      App = "longlivethebat"
      }
      }

      spec {
      replicas = 2
      selector {
      match_labels = {
      App = "longlivethebat"
      }
      }
      template {
      metadata {
      labels = {
      App = "longlivethebat"
      }
      }
      spec {
      container {
      image = "nginx:1.7.8"
      name = "batman"

      port {
      container_port = 80
      }

      resources {
      limits = {
      cpu = "0.5"
      memory = "512Mi"
      }
      requests = {
      cpu = "250m"
      memory = "50Mi"
      }
      }
      }
      }
      }
      }
      }
  • CLI

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # DEPLOY THE EKS CLUSTER
    terraform init
    terraform plan
    terraform apply
    # Configure kubectl to interact with the cluster
    aws eks --region $(terraform output -raw region) update-kubeconfig --name $(terraform output -raw cluster_name)
    # verify deployment
    kubectl get cs

    # DEPLOY NGINX PODS
    # add lab_kubernetes_resources.tf file
    terraform plan
    terraform apply
    # verify 2 "long-live-the-bat" pods are up and running
    kubectl get deployments

    # CLEAN UP
    terraform destroy
    # verify
    terraform show

Troubleshooting

graph LR

subgraph Primary UI
A[configuration language]
end

subgraph Metadata
B[state]
end

subgraph Resource graph comms
C[TF core application]
end

subgraph Auth mapping
D[Cloud Provider]
end

A --> B
B --> C
C --> D
  • Detect syntax errors on files

    1
    terraform fmt
  • Detect errors on dependencies (e.g. cycles, invalid references, unsupported group vars)

    1
    terraform validate
  • Detect version missmatching

    1
    terraform version
  • Get TF trace, setting environment variables

    1
    2
    3
    4
    export TF_LOG_CORE=TRACE
    export TF_LOG_PROVIDER=TRACE
    export TF_LOG_PATH=logs.txt
    terraform refresh
  • Detect state discrepancies

    1
    terraform apply