それなりに適当にやってます

なんとなくそれっぽいメモとか備忘録とか適当に。 2018年5月にブログ移転しました。 古い記事は未整理です。

Terraform + GCP をもう少しやってみる #1

前にTerraformを入れて実行するところまでやって、次に Packer + Ansible でイメージを作成するところまでやったので、次にもう少し突っ込んだ構成を組んでみる。

やってみたいこと

  • Packer+Ansible
    • 先にPacker+Ansibleでベースになるイメージを作成
  • Terraform
    • インスタンス・テンプレートを作成
    • インスタンス・グループの作成(マルチゾーン)
    • HTTP負荷分散の作成
    • CloudSQLの作成(DB作成~HAまで)
  • Packer+Ansible (Update)
    • CloudSQL Proxy を追加した更新イメージを作成
  • Terraform
    • 新イメージでアップデート > できなかった

Terraform のインストールと初期化

入れて動かすとこまでは前にやったのでその続きから。 GCP の Terraform 設定については、GitHub の Google Cloud Platform のリポジトリにサンプルがあるのでサンプルも参考にしつつやってみる。

$ export GOOGLE_APPLICATION_CREDENTIALS='account.json'      #<-今回は環境変数でサービスアカウントの認証情報(JSON)を指定
$ mkdir terraform-example
$ cd terraform-example
$ vi provider.tf
// Configure the Google Cloud provider
provider "google" {
  project     = "example-project"       #<-プロジェクトのIDは別途確認
  region      = "asia-northeast1"
}
$ terraform init

Packer + Ansible

前回やったことを参照、とりあえずデプロイしてイメージ作成までした。

Terraform の設定

バックエンドの設定

Teraform の状態が保持されている .tfstate ファイルの保存先を GCS (Google Cloud Storage) にする (ドキュメント参照)

$ vi backend.tf
terraform {
  backend "gcs" {
    bucket  = "tf-state-prod"       #<-事前にバケットを作成しておく
    prefix  = "terraform/state"     #<-バケット名/terraform/state 下に .tfstate のファイルが置かれる
  }
}

インスタンス・テンプレートの作成

Terraform のドキュメントを参考に作成する。

$ vi template.tf
resource "google_compute_instance_template" "packer-example-template" {     #<-resource の名前は terrafrom 内の管理上の名前なのでなんでも良い
  name                 = "poacker-example-template-"
  machine_type         = "n1-standard-1"
  can_ip_forward       = false

  tags = ["http-server","https-server"]     #<-ネットワークタグ

  scheduling {
    automatic_restart   = true
    on_host_maintenance = "MIGRATE"
  }

  disk {
    source_image = "packer-1527743285"
    auto_delete  = true
    boot         = true
  }

  network_interface {
    network = "default"
  }
  
  lifecycle {
    create_before_destroy = true
  }
  
  service_account {
    scopes = ["userinfo-email", "compute-ro", "storage-ro", "sql-admin"]
  }
}

ここまでの内容をデプロイして確認する

$ terraform plan                    # Dry-Run
$ terraform apply                   # GCPへのデプロイ

この時点で テンプレートが作成されたこと。 Cloud Storage に .tfstate のファイルが作成されたことがわかる。 ついでに terraform show で確認し一旦削除する。

$ terraform show                    # デプロイ後の情報を確認
$ terraform destroy                 # デプロイ環境の削除

マルチゾーン・インスタンスグループ と ヘルスチェックの作成

次に ヘルスチェックマルチゾーン・インスタンスグループ を作成する。

ヘルスチェック

$ vi healthcheck.tf
resource "google_compute_health_check" "packer-example-health-check" {
  name = "packer-example-health-check"

  timeout_sec         = 5
  check_interval_sec  = 5
  healthy_threshold   = 1
  unhealthy_threshold = 5
  
  http_health_check {
    request_path = "/"
    port = "80"
  }
}

マルチゾーン・インスタンスグループ

$ vi instance-group.tf
resource "google_compute_region_instance_group_manager" "packer-example-instance-group" {
  name = "packer-example-instance-group"

  base_instance_name         = "packer-group-instance"
  instance_template          = "${google_compute_instance_template.packer-example-template.self_link}"
  region                     = "asia-northeast1"
  distribution_policy_zones  = ["asia-northeast1-a", "asia-northeast1-b", "asia-northeast1-c"]

  target_size  = 3

  named_port {
    name = "http"
    port = 80
  }

  auto_healing_policies {
    health_check      = "${google_compute_health_check.packer-example-health-check.self_link}"
    initial_delay_sec = 300
  }
}

この段階で terraform plan, apply などを実行してインスタンスグループが作成されるところまで確認

HTTPターゲットプロキシ(HTTP負荷分散)

次に負荷分散するために HTTPターゲットプロキシ を設定する・・・けど少し複雑

ちょっとよくわからないという場合は、インスタンスグループをデプロイした状態で手動で負荷分散を設定してみたらイメージできると思う。

$ vi http_proxy.tf
resource "google_compute_target_http_proxy" "packer-example-http-proxy" {
  name        = "packer-example-http-proxy"
  url_map     = "${google_compute_url_map.packer-example-url-map.self_link}"
}

resource "google_compute_url_map" "packer-example-url-map" {
  name        = "packer-example-url-map"

  default_service = "${google_compute_backend_service.packer-example-backend.self_link}"

  host_rule {
    hosts = ["*"]
    path_matcher = "allpaths"
  }

  path_matcher {
    name            = "allpaths"
    default_service = "${google_compute_backend_service.packer-example-backend.self_link}"

    path_rule {
      paths   = ["/*"]
      service = "${google_compute_backend_service.packer-example-backend.self_link}"
    }
  }
}

resource "google_compute_backend_service" "packer-example-backend" {
  name        = "packer-example-backend"
  port_name   = "http"
  protocol    = "HTTP"
  timeout_sec = 10
  enable_cdn  = false

  backend {
    group = "${google_compute_region_instance_group_manager.packer-example-instance-group.instance_group}"
  }

  health_checks = ["${google_compute_http_health_check.packer-example-http-health-check.self_link}"]
}

resource "google_compute_http_health_check" "packer-example-http-health-check" {
  name               = "packer-example-http-health-check"
  request_path       = "/"
  check_interval_sec = 5
  timeout_sec        = 5
}

この段階で terraform plan, apply などを実行して各ヘルスチェックが正常なこと。負荷分散されたコンテンツが見れていることを確認

CloudSQL

次に CloudSQL を追加する。 設定の詳細については以下を参照

インスタンスを作成 > DBを作成 > ユーザを追加 ドキュメントの注意書きの通り既定の root ユーザは削除されるののでユーザは必須、あと depends_on で依存関係(リソースの作成順)を明示し、レプリカを最後にしないと駄目だった。

$ vi cloudsql.tf
## MASTER
resource "google_sql_database_instance" "master" {
  name = "packer-example-master"
  database_version = "MYSQL_5_7"    #<-バージョンはTerraformのドキュメントや、GCPのコンソールで確認
  region = "asia-northeast1"

  settings {
    tier = "db-f1-micro"

    backup_configuration {
      binary_log_enabled = "true"
      enabled            = "true"
      start_time         = "20:00"  #<-UTC
    }
    
    maintenance_window {
      day = "7"
      hour = "17"   #<-UTC
    }
  }
}

## REPLICA
resource "google_sql_database_instance" "replica" {
  depends_on = [
    "google_sql_database_instance.master",
    "google_sql_database.database",
    "google_sql_user.users"
  ]
  name = "packer-example-replica"
  database_version = "MYSQL_5_7"
  region = "asia-northeast1"

  master_instance_name = "${google_sql_database_instance.master.name}"
  replica_configuration {
    failover_target        = "true"
  }
  
  settings {
    tier = "db-f1-micro"
    maintenance_window {
      day = "7"
      hour = "17"   #<-UTC
    }
  }
}

resource "google_sql_database" "database" {
  depends_on = [
    "google_sql_database_instance.master"
  ]
  name      = "packer-example-database"
  instance  = "${google_sql_database_instance.master.name}"
  charset   = "utf8"
  collation = "utf8_general_ci"
}

resource "google_sql_user" "users" {
  depends_on = [
    "google_sql_database_instance.master"
  ]
  name     = "dbuser"
  instance = "${google_sql_database_instance.master.name}"
  host     = "%"
  password = "changeme"
}

この段階で terraform plan, apply などを実行して正常なこと、フェイルオーバーができることを確認(フェイルオーバーが可能になるまで少し時間がかかる)

Packer + Ansible の更新

CloudSQL の作成までできたので、試しに Packer + Ansible で作成していたイメージを更新して CloudSQL Proxy を組み込んでみる。下記のような内容を Ansible Playbook に反映して build した。

  vars:
    project_id: example_project
    region: asia-northeast1
    cloudsql_name: example_project:asia-northwast1:cloudsql
  tasks:
    - name: install mysql
      yum: name=mysql

    - name: Download file and force basic auth
      get_url: url=https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64
               dest=/usr/local/bin/cloud_sql_proxy

    - name: chmod cloud_sql_proxy
      file: path=/usr/local/bin/cloud_sql_proxy
            owner=root group=root mode=0755

    - name: create systemd.service file
      blockinfile:
        path: /etc/systemd/system/cloudsql-proxy.service
        create: yes
        block: |
          [Unit]
          Description = CloudSQL Proxy Default
          After = network.target

          [Service]
          ExecStart = /usr/local/bin/cloud_sql_proxy -instances={{ project_id }}:{{ region }}:{{ cloudsql_name }}=tcp:3306
          ExecStop = /bin/kill ${MAINPID}
          ExecReload = /bin/kill -HUP ${MAINPID}
          Restart = always
          Type = simple

          LimitNOFILE=65535
          LimitNPROC=65535

          [Install]
          WantedBy = multi-user.target

    - name: systemctl reload
      systemd: daemon_reload=yes

    - name: start cloudsql-proxy
      service: name=cloudsql-proxy state=restarted enabled=yes

Terraform の更新

上記で作成したイメージを指定して、Terraform で作成していた環境を更新してみる。 インスタンス・テンプレート(template.tf)の source_image を新しく作成したイメージに変更して terraform plan で確認する。

$ vi template.tf        #<-source_image を新たに作成したイメージに変更
$ terraform plan

するとテンプレートが一度削除されて、再作成されることがわかる。これはドキュメントに書いてある通り

-/+ google_compute_instance_template.packer-example-template (new resource required)
      id:                                     "packer-example-template" => <computed> (forces new resource)
      ...(略)
      disk.0.source_image:                    "packer-example-1527753654" => "packer-example-1527838317" (forces new resource)
      ...

問題なければこのまま更新してみる。

$ terraform apply

・・・。

provider/google incorrect update order between instance template and group manager #11905

(´・ω・`)

次にやること

  • Terraform
  • 非マネージドなインスタンス・グループの構成も試す
  • その他 Terrafrom / GCP設定の追加・連携
    • Ansible 含む (SendGridの設定とかも)
  • Git, CI/CD と連携したコンテンツの配信・更新

参考URL

以上