17
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Google Cloud PlatformAdvent Calendar 2019

Day 14

Terraformを用いてVPCネットワークにGKE限定公開クラスタを構成する

Last updated at Posted at 2019-12-14

この記事は「Google Cloud Platform Advent Calendar 2019」14日目の記事です。

はじめに

GCPを勉強し始めて一ヶ月程になり、VPCネットワークについてはまだふわふわした理解だったのですが、そろそろ基本や仕様を理解しないとな〜と思い、Terraformを利用して限定公開クラスタを作成したことについて投稿します:writing_hand:

限定公開クラスタとは?

**限定公開クラスタ**とは、簡単に述べるとプライベートIPで接続できるGKEクラスタです。(そのまんま:bow:
通常GKEクラスタを作成すると外部IPが作成されインターネット経由でアクセス可能となりますが、オンプレ環境のようにプライベートなクラスタを利用したい場合は限定公開として作成することが可能です。
下記のような特徴を持っています。

  • マスターノードにアクセスするトラフィックを**マスター承認済みネットワーク(ホワイトリスト)**によって制御可能です。
  • ワーカーノードにはVPCネットワークの内部IPアドレスを用いてアクセス可能です。

マスターへのアクセスについて

マスターへのパブリックエンドポイントマスター承認済みネットワークを組み合わせることで、マスターノードへのアクセスを段階的に制御することが可能となります。

組み合わせとして下表のように3パターン存在しています。(詳細については見出しのリンク先をご確認ください。)
今回は(A)の構成で作成しており、内部IPのみでアクセス可能です。

||パブリックエンドポイント|マスター承認済みネットワーク|説明|
|---|---|---|---|---|
|(A)|無効|(自動有効)|事前定義した内部IPアドレスのみアクセス可能|
|(B)|有効|有効|事前定義した内部/外部IPアドレスがアクセス可能|
|(C)|有効|無効|全ての任意のIPアドレスからアクセス可能|

Terraformとは?

TerraformはHashiCorp社が提供しているインフラストラクチャをコードとして管理するためのIaCに分類されるツールです。インフラをコードとして管理可能になるためバージョン管理や設計レビューなどが容易になります。他にもコマンド実行でインフラ環境が構築されるため自動化にも利用しやすい、複雑なリソースの依存関係を解決してくれるなど様々なメリットがあります。

■ Terraformによるインフラ定義

Terraformでは**HCL(HashiCorp Configuration Language)**でインフラを定義します。
下記はGCEインスタンスを作成する例です。コマンド叩くとGCEインスタンスがサクッと作成されちゃいます。

gce.tf
resource "google_compute_instance" "default" {
  name         = "test"
  machine_type = "n1-standard-1"
  zone         = "us-central1-a"
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-9"
    }
  }

  // Local SSD disk
  scratch_disk {
    interface = "SCSI"
  }

  network_interface {
    network = "default"

    access_config {
      // Ephemeral IP
    }
  }

  service_account {
    scopes = ["userinfo-email", "compute-ro", "storage-ro"]
  }
}

作成するネットワーク構成

今回は下図のようなネットワークを構築しました。外部公開インスタンスを踏み台にしていますが、通常はCloud VPNやCloud Interconnectを利用するのかな、と思います。GKEクラスタのネットワークが自分には難しくて間違っていたらすみません:sweat_smile:

5.cluster-network.png

構築手順

ここからは長々となりますが、Terraformの設定手順を記載します。

  1. 準備
  2. ネットワークの作成
  3. パブリックインスタンスの作成
  4. プライベートインスタンスの作成
  5. 限定公開クラスタの作成

※所々Tipsのように書いた説明は、ハマったポイントなので記載しました:scream:

環境

  • MacOS 10.15.2
  • Google Cloud SDK 273.0.0
  • tfenv 1.0.2
  • terraform v0.12.17

1.準備

Terraformのインストール

手順は省略しますが、tfenv経由でインストールしました。

GCPの準備

GCPプロジェクトを作成して、課金アカウントを設定します。
今回利用するGCE/GKE/DNSに関するAPIを有効にします。

$ gcloud config set project $PROJECT_ID
$ gcloud services enable \
  compute.googleapis.com \
  container.googleapis.com \
  dns.googleapis.com

2. ネットワークの作成

基本となるVPCネットワークmy-pub-nwmy-priv-nwを作成し、VPCピアリングを設定します。

2.network.png

2-1. ネットワーク作成

サブネットは自動作成させないためにauto_create_subnetworks=falseを指定しています。
外部公開インスタンス用のVPCネットワークmy-pub-nwを作成します。

my_pub_nw.tf
resource "google_compute_network" "my_pub_nw" {
  name                    = var.my_pub_nw_name
  project                 = var.project
  auto_create_subnetworks = false 
}

限定公開インスタンス/クラスタ用のVPCネットワークmy-priv-nwを作成します。

my_priv_nw.tf
resource "google_compute_network" "my_priv_nw" {
  name                    = var.my_priv_nw_name
  project                 = var.project
  auto_create_subnetworks = false 
}

2-2. ファイアウォール設定

内部IPアドレス10.0.0.0/8からの受信パケットを全て許可するファイアウォールを設定します。
(デフォルトネットワークのdefault-allow-internalと同様のもの)

my_pub_nw_firewall.tf
resource "google_compute_firewall" "my_pub_nw_allow_ingress_internal" {
  name          = "my-pub-nw-allow-ingress-internal"
  network       = google_compute_network.my_pub_nw.self_link
  source_ranges = ["10.0.0.0/8"]

  allow {
    protocol = "icmp"
  }

  allow {
    protocol = "tcp"
    ports    = ["0-65535"]
  }

  allow {
    protocol = "udp"
    ports    = ["0-65535"]
  }
}

暗黙のファイアウォールルールについて

Cloud Consoleなどの画面上には表示されませんが、暗黙のファイアウォールルールが存在しています。
これらのルールは優先度が限りなく低く設定されているので(65535)、優先度が高い(数値の小さい)ルールでオーバライドが可能です。

  • 暗黙の下り許可ルール(EGRESS):
     すべてのインスタンスは、任意の宛先にトラフィックを送信できます。
  • 暗黙の上り拒否ルール(INGRESS):
     受信トラフィックをブロックすることによって、すべてのインスタンスが保護されます。

defaultネットワークの事前設定ルールについて

デフォルトネットワークdefaultに事前設定されるファイアウォールルールの中にdefault-allow-internalがあります。
これは事前定義のIPアドレス範囲10.128.0.0/9に対してすべてのプロトコルとポートの上り接続を許可し、インスタンス間のトラフィックを受信します。


2-3. VPCピアリング

my-pub-nw,my-priv-nw間のVPCピアリングを設定します。
双方向に設定することではじめて利用可能となります。(片方だけだと待機状態)

my_peering.tf
resource "google_compute_network_peering" "my_pub_priv_peering" {
  name         = "my-pub-priv-peering"
  network      = google_compute_network.my_pub_nw.self_link
  peer_network = google_compute_network.my_priv_nw.self_link
}

resource "google_compute_network_peering" "my_priv_pub_peering" {
  name         = "my-priv-pub-peering"
  network      = google_compute_network.my_priv_nw.self_link
  peer_network = google_compute_network.my_pub_nw.self_link
}

2-4. DNS設定

限定公開ネットワークからGoogle APIs、Container RegistryにアクセスするためのDNS設定を追加します。
具体的には*.googleapis.com*.gcr.iorestricted.googleapis.com199.36.153.4/30)に解決させます。

google-apis.tf
resource "google_dns_record_set" "google_apis_cname" {
  project      = var.project
  managed_zone = "google-apis"
  name         = "*.${google_dns_managed_zone.google_apis.dns_name}"
  type         = "CNAME"
  ttl          = 300

  rrdatas      = ["restricted.googleapis.com."]
}

resource "google_dns_record_set" "google_apis_a" {
  project      = var.project
  managed_zone = "google-apis"
  name         = "restricted.googleapis.com."
  type         = "A"
  ttl          = 300

  rrdatas      = ["199.36.153.4","199.36.153.5","199.36.153.6","199.36.153.7"]
}

resource "google_dns_managed_zone" "google_apis" {
  project    = var.project
  name       = "google-apis"
  dns_name   = "googleapis.com."
  visibility = "private"

  private_visibility_config {
    networks {
      network_url = var.my_priv_nw_url
    }
  }
}

GKE 限定公開クラスタのContainerRegistryの設定について

限定公開のクラスタがGCRを利用する場合はDNSの設定が必要となります。
*.gcr.io宛ての通信を199.36.153.4/30に解決させることでGCRへアクセス可能となります。


gcr-io.tf
resource "google_dns_record_set" "gcr-io-cname" {
  project      = var.project
  managed_zone = "gcr-io"
  name         = "*.${google_dns_managed_zone.gcr_io.dns_name}"
  type         = "CNAME"
  ttl          = 300

  rrdatas      = ["gcr.io."]
}

resource "google_dns_record_set" "gcr-io-a" {
  project      = var.project
  managed_zone = "gcr-io"
  name         = "gcr.io."
  type         = "A"
  ttl          = 300

  rrdatas      = ["199.36.153.4","199.36.153.5","199.36.153.6","199.36.153.7"]
}

resource "google_dns_managed_zone" "gcr_io" {
  project    = var.project
  name       = "gcr-io"
  dns_name   = "gcr.io."
  visibility = "private"

  private_visibility_config {
    networks {
      network_url = var.my_priv_nw_url
    }
  }
}

オンプレミス ホスト用の限定公開の Google アクセスの構成について

限定公開のクラスタがGoogle APIsを利用する場合はDNSの設定が必要となります。
*.googleapis.com宛のすべてのリクエストを199.36.153.4/30に解決させることでGoogle APIsにアクセス可能となります。


3. パブリックインスタンスの作成

続いて、外部公開用のインスタンスを作成します。
3.public-instance.png

3-1. サブネットの設定

外部公開インスタンスを接続するためのサブネットmy-pub-subnetを作成します。

my_pub_nw.tf
resource "google_compute_subnetwork" "my_pub_subnet" {
  name          = var.my_pub_subnet_name
  ip_cidr_range = var.my_pub_subnet_ip_cidr
  region        = var.region
  network       = google_compute_network.my_pub_nw.self_link
}

3-2. 外部公開インスタンスの作成

外部公開インスタンスmy-pub-instanceを作成します。
google_compute_addressリソースで静的IPアドレスを作成しインスタンスのNICに適用します。
スコープにはcloud-platformを指定しており(理由は後述)、これによりCloud SDKのコマンドによるGoogle APIが利用可能となります。

my_pub_instance.tf
resource "google_compute_instance" "my_pub_instance" {
  project       = var.project
  zone          = var.zone
  name          = var.my_pub_instance_name # "my-pub-instance"
  machine_type  = var.machine_type

  tags = [var.my_pub_instance_name]

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-9"
    }
  }
  
  network_interface {
    subnetwork         = var.my_pub_subnet_name
    subnetwork_project = var.project
    access_config {
      nat_ip = google_compute_address.static_ip.address # 下記の外部IPを指定
    }
  }

  service_account {
    scopes = ["cloud-platform"]
  }
}   

resource "google_compute_address" "static_ip" { # 外部IPアドレス
  name = "my-pub-instance-address"
}

アクセススコープについて

アクセススコープは、インスタンスの権限を指定するレガシーな方法で、
現在は完全な権限アクセススコープであるcloud-platformを設定しサービスアカウントに適切なIAM役割を付与してアクセス制御することが推奨されているようです。
また、作成したインスタンスには下記のアクセススコープが自動的に構成されています。

- Google Cloud Storage に対する読み取り専用アクセス権
- Compute Engine ログに書き込むための書き込みアクセス権
- Google Cloud プロジェクトに指標データを公開するための書き込みアクセス権
- Google Cloud Endpoints に必要な Service Management 機能に対する読み取り専用アクセス権(アルファ版)
- Google Cloud Endpoints に必要な Service Control 機能に対する読み取り書き込みアクセス権(アルファ版)

3-3. ファイアウォールの設定

外部公開インスタンスへのアクセスtcp:22icmpを許可します。

my_pub_nw_firewall.tf
resource "google_compute_firewall" "my_pub_nw_allow_ingress_my_instance" {
  name    = "my-pub-nw-allow-ingress-my-instance"
  network = google_compute_network.my_pub_nw.self_link

  allow {
    protocol = "icmp"
  }

  allow {
    protocol = "tcp"
    ports    = ["22"]
  }

  target_tags = ["my-pub-instance"]
}

これで、外部公開インスタンスの作成は完了です。

4. 限定公開インスタンスの作成

続いて、限定公開用のインスタンスを作成します。

4,private-instance.png

4-1. サブネットの作成

限定公開用のインスタンス/クラスタを接続するサブネットmy-priv-subnetを作成します。
内部IPからAPIを利用するためprivate_ip_google_access = trueを指定します。
GKE用のセカンダリIP範囲をsecondary_ip_rangeで指定しておきます。

my_priv_nw.tf
resource "google_compute_subnetwork" "my_priv_subnet" {
  name                     = var.my_priv_subnet_name
  ip_cidr_range            = var.my_priv_subnet_ip_cidr
  region                   = var.region
  network                  = google_compute_network.my_priv_nw.self_link
  private_ip_google_access = true
  secondary_ip_range {
    range_name    = "services"
    ip_cidr_range = var.my_priv_subnet_2nd_services_ip_cidr
  }
  secondary_ip_range {
    range_name    = "pods"
    ip_cidr_range = var.my_priv_subnet_2nd_pods_ip_cidr
  }
}

セカンダリIP範囲について

サブネットに対して複数のIP範囲を持たせることが可能です。プライマリに設定したものがサブネットのデフォルトのIP範囲として利用されますが、セカンダリに指定したIP範囲は今回のようにKubernetesのポッドやサービスのIPアドレスに割り当てることが可能となります。


4-2. プライベートインスタンスの作成

プライベートインスタンスmy-priv-instanceを作成します。

my_priv_instance.tf
resource "google_compute_instance" "my_priv_instance" {
  project       = var.project
  zone          = var.zone
  name          = var.my_priv_instance_name
  machine_type  = var.machine_type

  tags = [var.my_priv_instance_name]

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-9"
    }
  }
  
  network_interface {
    subnetwork         = var.my_priv_subnet_name
    subnetwork_project = var.project
  }

  service_account {
    scopes = ["cloud-platform"]
  }

  scheduling {
    automatic_restart = false
    preemptible       = true
  }
  metadata_startup_script = "apt-get install kubectl"
}   

4−3. ファイアウォールの設定

外部公開ネットワーク10.10.10.0/24からのicmptcp:22,80,443を許可します。

my_priv_nw_firewall.tf
resource "google_compute_firewall" "my_priv_instance_allow_ingress_my_pub_instance" {
  name          = "my-priv-instance-allow-ingress-my-pub-instance"
  network       = google_compute_network.my_priv_nw.self_link
  source_ranges = ["10.10.10.0/24"]
  target_tags   = ["my-priv-instance"]

  allow {
    protocol = "icmp"
  }

  allow {
    protocol = "tcp"
    ports    = ["22", "80", "443"]
  }
}

5. 限定公開クラスタの作成

いよいよ限定公開クラスタを作成します。

5.cluster-network.png

5-1. ファイアウォールの設定

下記のファイアウォールを設定することで、GCRからコンテナイメージをpullできるようになります。
設定に失敗するとポッドのコンテナが立ち上がりません。。

my_priv_nw_firewall.tf
resource "google_compute_firewall" "my_priv_nw_allow_egress_google_apis" {
  name               = "my-priv-nw-allow-egress-google-apis"
  network            = google_compute_network.my_priv_nw.self_link
  direction          = "EGRESS"
  destination_ranges = ["199.36.153.4/30"]

  allow {
    protocol = "all"
  }
}

また、下記の設定はクラスタを作成する前に作成しておく必要があるようです。
(ワーカーノードが接続するmy_priv_nwからマスターノードへトラフィックが許可されないため)

my_priv_nw_firewall.tf
resource "google_compute_firewall" "my_priv_nw_allow_egress_masternode" {
  name               = "my-priv-nw-allow-egress-masternode"
  network            = google_compute_network.my_priv_nw.self_link
  direction          = "EGRESS"
  destination_ranges = [var.my_priv_cluster_master_ip_cidr] # "172.16.0.0/28"

  allow {
    protocol = "tcp"
    ports    = ["443", "10250"]
  }
}

5-2. クラスタの作成

限定公開クラスタmy-priv-clusterを作成します。今回の肝ですので各blockを確認します。

private_cluster_configブロック

private_cluster_configブロックは、限定公開の設定となります。

  • enable_private_nodes: 各ノードのパブリックIPを無効化します。
  • enable_private_endpoint: マスターノードのパブリックエンドポイントを無効化します。
  • master_ipv4_cidr_block: マスターノードに割り当てるIP範囲を指定しています。

ip_allocation_policyブロック

ip_allocation_policyブロックは、IP割当設定になります。サブネット作成時に作成したセカンダリIP範囲を利用しています。

  • cluster_secondary_range_name: クラスタのポッドに割り当てるIP範囲を指定します。
  • services_secondary_range_name: クラスタのサービスドに割り当てるIP範囲を指定します。

master_authorized_networks_configブロック

master_authorized_networks_configブロックはマスター承認済みネットワークに関する設定となります。
ここではmy-priv-instanceが接続されているmy-priv-subnetを指定しています。

my_priv_cluster.tf
resource "google_container_cluster" "primary" {
  name               = var.my_priv_cluster_name
  location           = var.zone
  initial_node_count = 3
  network            = "my-priv-nw"
  subnetwork         = "my-priv-subnet"

  private_cluster_config {
    enable_private_nodes    = true
    enable_private_endpoint = true
    master_ipv4_cidr_block  = var.my_priv_cluster_master_ip_cidr # "172.16.0.0/28"
  }

  ip_allocation_policy {
    cluster_secondary_range_name   = "pods"
    services_secondary_range_name  = "services"
  }

  master_authorized_networks_config {
    cidr_blocks {
      cidr_block   = var.my_priv_subnet_ip_cidr
      display_name = "net1"
    }
  }
}

マスター認証済ネットワークの設定について

マスターノードのエンドポイントにアクセスするIP範囲をホワイトリストとして登録することで、登録されていないIPからのトラフィックを制御することが可能となります。


以上の手順で限定公開クラスタの作成完了です。
クラスタには内部IPアドレス経由のアクセスのみ可能となっています。

まとめ

今回はTerraformを利用してGKE限定公開クラスタを作成してみました。
多少複雑な点もいくつかありましたが、インフラ経験一週間の私でもお手軽に構成することができました。
限定公開クラスタが必要となるケースは業務でもあるかと思いますので、その際は是非利用してみてはいかがでしょうか。

個人的にはGKE(と言うよりKubernetes)に対する理解不足によりハマったことが多かったのでこれからも勉強します。:muscle:

参考

インターネット接続なしの完全にプライベートなGKEクラスター
GKE/Kubernetes でなぜ Pod と通信できるのか

17
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?