2021年12月24日 星期五

AWS VPC Peer Connection helping EKS (Private Gateway) connect bidirectionally to RDS Routing by Terraform

這一陣子在處理許多 DevOps 的問題,遇到了要讓 EKS Cluster 存取不同 VPC 服務之間的問題,在 AWS 架構下可以使用 VPC 的 Peer Connection 來讓兩個不同 VPC 的網路對聯,交換 Route Table 地址,這樣彼此就可以發現對方。


原理


除了讓 A, B 對連線外,還會讓 A 服務的 Network Route Table 知道 B 的 Network CIDR Table,這樣 A, B 就可以存取(發現)到不同網段下的電腦,像是 BGP Routing Table 這樣。


但是不可以存取跟自己相同 VPC CIDR 的服務,那樣 Route Table 會亂掉,見下方注意事項。


注意


當實施 VPC Peer Connection 架構下,需要特別注意如果還想要存取更多不同 VPC 資源,就一定要注意 VPC CIDR 分配不可以衝突,因此一定要做好分配。


服務少的話,使用 172.16.0.0/12 架構來分配 AWS 上的資源應該是沒問題的,比方說 EKS (CIDR: 172.16.1.0/24) Peer Connection CIDR 172.16.20.0/24 下的網路,以此類推 10.0.0.0, 192.168...,其網段數量計算可以使用 subnet mask calculator 來看地址 cover 多少電腦。 (私人網段的 spec 可以參考 RFC 1918。)


也許一般來說用 Terraform 因為每個服務都當成 module 複製來複製去,鮮少需要改 VPC CIDR,但如果會發生這樣的狀況,那就一定要注意要手動去修改不同 VPC 的 CIDR,不要讓他們碰撞。


Terraform Module


這個功能會直接寫成模組,這個模組最主要的目的是讓兩個 VPC 可以做對連,兩個模組的 VPC ID 要使用最主要的那個 VPC。

由於本章節要解決的是讓 EKS 的 Terraform 模組可以連線到 RDS 服務上,以此作為範例,EKS 我使用的模組是 cloudposse 的,他在設定上會有點礙手礙腳,但總之要選用 eks 的 main VPC

對連的 Terraform 設定會碰到一個需要注意的問題,我將這個問題放到下一個小節提及。


在這個模組中最主要就是建立一個 aws_vpc_peering_connection、兩個 (對連雙方)aws_route 及 aws_security_group (用於對連連線安全組控制)。

建立後讓 aws_route 去套用同一個 aws_vpc_peering_connection。


而 security_group 則是要讓 eks 或 rds 各自的流量可以互通,我是開 all,但幾於資安考量的話,可以再鎖定到比較細的 security group 規則。


檔案結構:

  • main.tf
  • var.tf


main.tf:

resource "aws_vpc_peering_connection" "PEER_A2B" {
  
  peer_vpc_id   = var.peer_vpc_id
  vpc_id        = var.this_vpc_id # acceptor is us

  auto_accept = true

  accepter {
    allow_remote_vpc_dns_resolution = true
  }

  requester {
    allow_remote_vpc_dns_resolution = true
  }
}

# bind to existsed rotuer table 

# aws router (e.g rds to eks)
resource "aws_route" "A2B" {
  count = length(var.this_vpc_route_table_ids)
  
  # this
  route_table_id            = var.this_vpc_route_table_ids[count.index]

  # to b
  destination_cidr_block    = var.peer_vpc_cidr

  vpc_peering_connection_id = aws_vpc_peering_connection.PEER_A2B.id
}

# aws router (e.g eks to rds)
resource "aws_route" "B2A" {
  count = length(var.peer_vpc_route_table_ids)

  # peer
  route_table_id            = var.peer_vpc_route_table_ids[count.index]

  # to a
  destination_cidr_block    = var.this_vpc_cidr

  vpc_peering_connection_id = aws_vpc_peering_connection.PEER_A2B.id
}

# These may cause aws_security_group_rule everytime modify needs to apply two time...  (WARNING)
resource "aws_security_group_rule" "A" {
  description = "Peering Config A (terraform-managed)"
  # count = length([var.peer_vpc_cidr]) could be more but only do once now

  type              = "ingress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = [var.peer_vpc_cidr]
  security_group_id = var.this_vpc_security_group_id
}

// InvalidPermission.Duplicate: because everyone apply the same rule
resource "aws_security_group_rule" "B" {
  description = "Peering Config B (terraform-managed)"
  # count = length(var.this_vpc_cidr) could be more but only do once now

  type              = "ingress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = [var.this_vpc_cidr]
  security_group_id = var.peer_vpc_security_group_id
}


var.tf:

variable "namespace" {
  type = string
}

variable "this_vpc_id" {
  type = string
}

variable "this_vpc_security_group_id" {
  type = string
}


variable "this_vpc_route_table_ids" {
  type = list(string)
}

variable "this_vpc_cidr" {
  type = string
}

variable "peer_vpc_id" {
  type = string
}

variable "peer_vpc_security_group_id" {
  type = string
}

variable "peer_vpc_route_table_ids" {
  type = list(string)
}

variable "peer_vpc_cidr" {
  type = string
}


叫用 Module:


module "peering_eks_to_rds" {
  source = "./modules/peering"

  namespace = "eks2rds_connector"

  this_vpc_id = module.my_db_instance.vpc_id
  this_vpc_route_table_ids = [module.my_db_instance.vpc_route_table_id]
  this_vpc_cidr = module.my_db_instance.vpc_cidr
  this_vpc_security_group_id = module.my_db_instance.db_security_group_id

  peer_vpc_id = module.my_eks.eks_vpc_id
  peer_vpc_route_table_ids = module.my_eks.vpc_all_route_table_ids
  peer_vpc_cidr = module.my_eks.vpc_cidr
  peer_vpc_security_group_id = module.my_eks.eks_cluster_security_group_id


  depends_on = [
    module.my_db_instance,
    module.my_eks
  ]
}


以上解釋一下關於 this_vpc_route_table_ids 會是陣列的原因,是因為有可能套用連線的 vpc 有好幾個 route table,就可以一併加上去,但如果只有一個,那就只填一個進陣列。


模組設計與要面臨的問題


我強烈建議先看完這個問題再開始進行 IaaC 調整,導入 VPC Peer Connection 進去你的架構時會碰到好幾個問題:

1. 你的 A, B 服務的 VPC 都必須存在,才可以建立 VPC Peer Connection,如果你的 A, B 是寫成 terraform module 時,不要把這個程式放到任一個 A, B 模組,要獨立成 C 模組額外套用,像上述用法一樣。  使用這個 VPC Peer Connection 時也要加上 depends_on 等待


2. 你的 A, B 服務的 VPC 是否有 CIDR 衝突,或甚至你其他服務有衝突 VPC,當你會開始考慮 VPC Peer Connection 時就表示未來你急有可能其他服務也都要做 Peer Connection ,最好現在就調整所有的 module CIDR。


以下提供我的 CIDR 設定:

  • eks 服務的 VPC 使用: 172.16.0.0/16
    • (以下都是 cloudposse 給定的)
    • public-apse1a: 172.16.96.0/19
    • public-apse1b: 172.16.128.0/19
    • public-apse1c: 172.16.160.0/19
    • private-apse1a: 172.16.0.0/19
    • private-apse1b: 172.16.32.0/19
    • private-apse1c: 172.16.64.0/19
  • rds 的 VPC 使用 10.0.0.0/16
    • public subnet: 10.0.1.0/24
    • private subnet: 10.0.0.0/24
  • mq 的 VPC 使用 10.11.0.0/16
    • public subnet: 10.11.1.0/24
    • private subnet: 10.11.0.0/24

我故意區分 eks 用 172.16 網段,但基本上整個 Class B 已經都被這個服務佔走了,其他服務就要改 Class A 級或 192.168 類型的私人網段,所以我隨便列了兩個服務的網段,特別填了不一樣的 Class A,要注意大多數 Terraform Module 都是複製即用,沒有做好 CIDR 管理就會發生太多的 VPC 都用了相同的 CIDR。

至少我在 rds, mq 或其他服務上使用了 10.0.0.0/8 級的分配,只要 10.1, 10.2, 10.3.... 這樣分下去,我最多可以有 254 個服務可以配置。


3. 你的 Terraform VPC 模組要是一開始就預先填上 route 進去,一定會導致 VPC Peer 模組套用時出現第一次都被刪掉,第二次才成功,第三次套用又被刪掉這種窘境 (Terraform double apply VPC route conflict) ,所以要注意,要抽離那個寫法,以下是範例:


resource "aws_route_table" "_" { vpc_id = aws_vpc._.id tags = { Name = "${var.namespace}-route-table" } // 不可以寫在這裡,這樣每次套用就會有機率衝突規則,導致時好時壞。 //shouldn't write in here, because this will conflict with vpc_peer_connection /*dynamic "route" { for_each = local.route content { cidr_block = route.value.cidr_block gateway_id = route.value.gateway_id instance_id = route.value.instance_id nat_gateway_id = route.value.nat_gateway_id } }*/ } // 應該多一個這個資源,套用到上面的 aws_route_table._ 這個 resource 來套用子資源,才不會發生衝突 resource "aws_route" "_" { count = length(local.route) route_table_id = aws_route_table._.id destination_cidr_block = local.route[count.index].cidr_block gateway_id = local.route[count.index].gateway_id instance_id = local.route[count.index].instance_id nat_gateway_id = local.route[count.index].nat_gateway_id }


補充,這裡連 security_group 的 ingress, engress 都要分開來寫,不可以寫在一起 (指寫在 aws_security_group 裡面),要另外寫在 aws_security_group_rule 裡面去套用,否則每次 apply 都會把 8 竿子打不著的服務重建 security group rule,而且還會打架,影響到 VPC Peer Connection 模組的功能。   記得,這個是指其他模組,不是只有在 VPC Peer Connection 的,建議是分開來寫。



你硬要寫在一起,被 VPC PeerConnection 模組引用的套件,每次 apply 功能都一定會這樣...


4. 套用完成後,記得去 VPC Peer Connection 服務上檢查上面的 Route Table 全部都出現了 A, B 服務,舉例假設我配 EKS + RDS,我應該要看到有 eks 的 route table,也有 rds 的 route table,而且兩邊的 security group 都有打通互相存取的權限 CIDR。


5. eks 要測有沒有連上 rds,要開一個 pods 然後 exec 連進去做測試,可以在裡面安裝個 pg_isready 去測試行不行


References:

https://dev.to/bensooraj/accessing-amazon-rds-from-aws-eks-2pc3
https://stackoverflow.com/questions/54596521/terraform-deletes-route-tables-then-adds-them-on-second-run-no-changes-bug-or

沒有留言:

張貼留言

© Mac Taylor, 歡迎自由轉貼。
Background Email Pattern by Toby Elliott
Since 2014