この記事は「Google Cloud Platform Advent Calendar 2019」14日目の記事です。
はじめに
GCPを勉強し始めて一ヶ月程になり、VPCネットワークについてはまだふわふわした理解だったのですが、そろそろ基本や仕様を理解しないとな〜と思い、Terraformを利用して限定公開クラスタを作成したことについて投稿します
限定公開クラスタとは?
**限定公開クラスタ**とは、簡単に述べるとプライベートIPで接続できるGKEクラスタです。(そのまんま)
通常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インスタンスがサクッと作成されちゃいます。
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クラスタのネットワークが自分には難しくて間違っていたらすみません
構築手順
ここからは長々となりますが、Terraformの設定手順を記載します。
- 準備
- ネットワークの作成
- パブリックインスタンスの作成
- プライベートインスタンスの作成
- 限定公開クラスタの作成
※所々Tipsのように書いた説明は、ハマったポイントなので記載しました
環境
- 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-nw
、my-priv-nw
を作成し、VPCピアリングを設定します。
2-1. ネットワーク作成
サブネットは自動作成させないためにauto_create_subnetworks=false
を指定しています。
外部公開インスタンス用のVPCネットワークmy-pub-nw
を作成します。
resource "google_compute_network" "my_pub_nw" {
name = var.my_pub_nw_name
project = var.project
auto_create_subnetworks = false
}
限定公開インスタンス/クラスタ用のVPCネットワークmy-priv-nw
を作成します。
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
と同様のもの)
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ピアリングを設定します。
双方向に設定することではじめて利用可能となります。(片方だけだと待機状態)
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.io
をrestricted.googleapis.com
の199.36.153.4/30)
に解決させます。
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へアクセス可能となります。
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-1. サブネットの設定
外部公開インスタンスを接続するためのサブネットmy-pub-subnet
を作成します。
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が利用可能となります。
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:22
、icmp
を許可します。
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-1. サブネットの作成
限定公開用のインスタンス/クラスタを接続するサブネットmy-priv-subnet
を作成します。
内部IPからAPIを利用するためprivate_ip_google_access = true
を指定します。
GKE用のセカンダリIP範囲をsecondary_ip_range
で指定しておきます。
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
を作成します。
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
からのicmp
とtcp:22,80,443
を許可します。
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-1. ファイアウォールの設定
下記のファイアウォールを設定することで、GCRからコンテナイメージをpullできるようになります。
設定に失敗するとポッドのコンテナが立ち上がりません。。
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
からマスターノードへトラフィックが許可されないため)
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
を指定しています。
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)に対する理解不足によりハマったことが多かったのでこれからも勉強します。
参考
インターネット接続なしの完全にプライベートなGKEクラスター
GKE/Kubernetes でなぜ Pod と通信できるのか